diff --git a/.appveyor.yml b/.appveyor.yml index 151c7b5a3..f5b18cc88 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -10,7 +10,7 @@ skip_commits: environment: python_stack: python 3.12 - FLUTTER_VERSION: 3.29.3 + FLUTTER_VERSION: 3.32.4 GITHUB_TOKEN: secure: 9SKIwc3VSfYJ5IChvNR74qi9xlUYK71gpBEZQSL4ZiqOEIAultwlQo3tHiHGLS8tz+EQtwMXEoWvw1Bl4y7oImJiH7lPjqo+BZnD7fzj9jwUYdDrP0u/HcmTxLHedH2b TWINE_USERNAME: __token__ @@ -33,7 +33,7 @@ environment: - job_name: Build Flet for macOS job_group: build_flet job_depends_on: build_flet_package - APPVEYOR_BUILD_WORKER_IMAGE: macos-monterey + APPVEYOR_BUILD_WORKER_IMAGE: macos-sonoma - job_name: Build Flet for Linux job_group: build_flet_linux @@ -68,12 +68,8 @@ environment: - job_name: Build Flet for web job_group: build_flet job_depends_on: build_flet_package - PYODIDE_URL: https://github.com/pyodide/pyodide/releases/download/0.27.5/pyodide-core-0.27.5.tar.bz2 - APPVEYOR_BUILD_WORKER_IMAGE: ubuntu2004 - - - job_name: Test Python 3.9 - job_group: python_tests - python_stack: python 3.9 + PYODIDE_URL: https://github.com/pyodide/pyodide/releases/download/0.27.7/pyodide-core-0.27.7.tar.bz2 + PYODIDE_CDN_URL: https://cdn.jsdelivr.net/pyodide/v0.27.7/full APPVEYOR_BUILD_WORKER_IMAGE: ubuntu2004 - job_name: Test Python 3.10 @@ -297,7 +293,7 @@ for: install: - . ci/common.sh - sudo apt update --allow-releaseinfo-change - - sudo apt install -y clang libgtk-3-dev + - sudo apt install -y clang libgtk-3-dev libasound2-dev - sh: | if [[ "$PACKAGE_NAME" == "flet-desktop" ]]; then sudo apt install -y libmpv-dev mpv libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio @@ -387,13 +383,14 @@ for: build_script: # Flutter Web client - pushd client - - flutter build web --release - - rm -rf build/web/canvaskit + - flutter build web --wasm - cp -R build/web $flet_sdk_root/packages/flet-web/src/flet_web # fix on mobile Safari: https://github.com/flutter/flutter/issues/145111#issuecomment-2714599139 - ls "$(dirname "$(command -v flutter)")/cache/flutter_web_sdk/flutter_js" - cp "$(dirname "$(command -v flutter)")/cache/flutter_web_sdk/flutter_js/flutter.js.map" $flet_sdk_root/packages/flet-web/src/flet_web/web - curl -L $PYODIDE_URL | tar -xj -C "$flet_sdk_root/packages/flet-web/src/flet_web/web" + - curl -L $PYODIDE_CDN_URL/packaging-24.2-py3-none-any.whl -o "$flet_sdk_root/packages/flet-web/src/flet_web/web/pyodide/packaging-24.2-py3-none-any.whl" + - curl -L $PYODIDE_CDN_URL/micropip-0.8.0-py3-none-any.whl -o "$flet_sdk_root/packages/flet-web/src/flet_web/web/pyodide/micropip-0.8.0-py3-none-any.whl" - popd - tar -czvf client/build/flet-web.tar.gz -C client/build/web . @@ -472,4 +469,4 @@ for: auth_token: $(GITHUB_TOKEN) release: $(APPVEYOR_REPO_TAG_NAME) on: - APPVEYOR_REPO_TAG: true \ No newline at end of file + APPVEYOR_REPO_TAG: true diff --git a/client/.fvmrc b/client/.fvmrc index b987073ac..984aec1cf 100644 --- a/client/.fvmrc +++ b/client/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.29.3" + "flutter": "3.32.4" } \ No newline at end of file diff --git a/client/android/app/build.gradle b/client/android/app/build.gradle index 5fea8634e..d43c0078b 100644 --- a/client/android/app/build.gradle +++ b/client/android/app/build.gradle @@ -25,7 +25,7 @@ if (flutterVersionName == null) { android { namespace "com.appveyor.flet" compileSdkVersion flutter.compileSdkVersion - ndkVersion "25.1.8937393" + ndkVersion "26.3.11579264" packagingOptions { jniLibs { diff --git a/client/ios/Podfile b/client/ios/Podfile index 279576f38..4e3872746 100644 --- a/client/ios/Podfile +++ b/client/ios/Podfile @@ -37,5 +37,23 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) + + target.build_configurations.each do |config| + # You can remove unused permissions here + # for more infomation: https://github.com/Baseflow/flutter-permission-handler/blob/main/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h + # e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0' + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ + '$(inherited)', + + ## dart: PermissionGroup.microphone + 'PERMISSION_MICROPHONE=1', + + ## dart: PermissionGroup.photos + 'PERMISSION_PHOTOS=1', + + ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse] + 'PERMISSION_LOCATION_WHENINUSE=1', + ] + end end end diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock index f8585860b..eb705d4e7 100644 --- a/client/ios/Podfile.lock +++ b/client/ios/Podfile.lock @@ -38,21 +38,10 @@ PODS: - DKImagePickerController/PhotoGallery - Flutter - Flutter (1.0.0) - - geolocator_apple (1.2.0): - - Flutter - - Google-Mobile-Ads-SDK (11.10.0): - - GoogleUserMessagingPlatform (>= 1.1) - - google_mobile_ads (5.2.0): - - Flutter - - Google-Mobile-Ads-SDK (~> 11.10.0) - - webview_flutter_wkwebview - - GoogleUserMessagingPlatform (2.7.0) - integration_test (0.0.1): - Flutter - media_kit_libs_ios_video (1.0.4): - Flutter - - media_kit_native_event_loop (1.0.0): - - Flutter - media_kit_video (0.0.1): - Flutter - package_info_plus (0.4.5): @@ -62,13 +51,10 @@ PODS: - FlutterMacOS - permission_handler_apple (9.3.0): - Flutter - - record_darwin (1.0.0): + - record_ios (1.0.0): - Flutter - - FlutterMacOS - rive_common (0.0.1): - Flutter - - screen_brightness_ios (0.1.0): - - Flutter - SDWebImage (5.20.0): - SDWebImage/Core (= 5.20.0) - SDWebImage/Core (5.20.0) @@ -78,8 +64,6 @@ PODS: - Flutter - FlutterMacOS - SwiftyGif (5.4.5) - - torch_light (0.0.1): - - Flutter - url_launcher_ios (0.0.1): - Flutter - volume_controller (0.0.1): @@ -95,21 +79,16 @@ DEPENDENCIES: - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) - - geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`) - - google_mobile_ads (from `.symlinks/plugins/google_mobile_ads/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`) - - media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`) - media_kit_video (from `.symlinks/plugins/media_kit_video/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - - record_darwin (from `.symlinks/plugins/record_darwin/ios`) + - record_ios (from `.symlinks/plugins/record_ios/ios`) - rive_common (from `.symlinks/plugins/rive_common/ios`) - - screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`) - sensors_plus (from `.symlinks/plugins/sensors_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - - torch_light (from `.symlinks/plugins/torch_light/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - volume_controller (from `.symlinks/plugins/volume_controller/ios`) - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) @@ -119,8 +98,6 @@ SPEC REPOS: trunk: - DKImagePickerController - DKPhotoGallery - - Google-Mobile-Ads-SDK - - GoogleUserMessagingPlatform - SDWebImage - SwiftyGif @@ -133,16 +110,10 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/file_picker/ios" Flutter: :path: Flutter - geolocator_apple: - :path: ".symlinks/plugins/geolocator_apple/ios" - google_mobile_ads: - :path: ".symlinks/plugins/google_mobile_ads/ios" integration_test: :path: ".symlinks/plugins/integration_test/ios" media_kit_libs_ios_video: :path: ".symlinks/plugins/media_kit_libs_ios_video/ios" - media_kit_native_event_loop: - :path: ".symlinks/plugins/media_kit_native_event_loop/ios" media_kit_video: :path: ".symlinks/plugins/media_kit_video/ios" package_info_plus: @@ -151,18 +122,14 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" - record_darwin: - :path: ".symlinks/plugins/record_darwin/ios" + record_ios: + :path: ".symlinks/plugins/record_ios/ios" rive_common: :path: ".symlinks/plugins/rive_common/ios" - screen_brightness_ios: - :path: ".symlinks/plugins/screen_brightness_ios/ios" sensors_plus: :path: ".symlinks/plugins/sensors_plus/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" - torch_light: - :path: ".symlinks/plugins/torch_light/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" volume_controller: @@ -177,32 +144,25 @@ SPEC CHECKSUMS: device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 - file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517 + file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - geolocator_apple: d981750b9f47dbdb02427e1476d9a04397beb8d9 - Google-Mobile-Ads-SDK: 13e6e98edfd78ad8d8a791edb927658cc260a56f - google_mobile_ads: dc2b2a5884bef7ab2b4ff30022a513df5373e208 - GoogleUserMessagingPlatform: a8b56893477f67212fbc8411c139e61d463349f5 integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854 - media_kit_native_event_loop: 5fba1a849a6c87a34985f1e178a0de5bd444a0cf media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474 - package_info_plus: 580e9a5f1b6ca5594e7c9ed5f92d1dfb2a66b5e1 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d - record_darwin: 1630616226de4038fa17cec21b11403ca510ec3e + record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b rive_common: dd421daaf9ae69f0125aa761dd96abd278399952 - screen_brightness_ios: 5ed898fa50fa82a26171c086ca5e28228f932576 SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8 - sensors_plus: 1c5f0a01ce21c609a4df404c4e6879d62bce287f + sensors_plus: 6a11ed0c2e1d0bd0b20b4029d3bad27d96e0c65b shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - torch_light: d093d579a221a59ef8a6b8c0eca20d52f7178087 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d - volume_controller: ca1cde542ee70fad77d388f82e9616488110942b - wakelock_plus: fd58c82b1388f4afe3fe8aa2c856503a262a5b03 - webview_flutter_wkwebview: 44d4dee7d7056d5ad185d25b38404436d56c547c + volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12 + wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 + webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2 -PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 +PODFILE CHECKSUM: 8d1bc72cfc7d4e7c18bda0011338a2a374fe0e56 COCOAPODS: 1.16.2 diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 51ef87bc7..979c654f0 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -139,7 +139,7 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 58A4996EE779406784CD4C79 /* [CP] Embed Pods Frameworks */, - 2297FE9C08FD9A4A6B4637CC /* [CP] Copy Pods Resources */, + 66843B3D06C83DEA7E765D27 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -198,23 +198,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 2297FE9C08FD9A4A6B4637CC /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -270,6 +253,23 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + 66843B3D06C83DEA7E765D27 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; diff --git a/client/ios/Runner/Info.plist b/client/ios/Runner/Info.plist index 652a45cda..5c38e1245 100644 --- a/client/ios/Runner/Info.plist +++ b/client/ios/Runner/Info.plist @@ -47,10 +47,7 @@ ITSAppUsesNonExemptEncryption - NSPhotoLibraryUsageDescription - The app needs access to photo library, so that photos can be selected. - GADApplicationIdentifier - ca-app-pub-3940256099942544~1458002511 + UIApplicationSupportsIndirectInputEvents NSAppTransportSecurity @@ -58,9 +55,23 @@ NSAllowsArbitraryLoads + + + NSPhotoLibraryUsageDescription + The app needs access to photo library, so that photos can be selected. + + + GADApplicationIdentifier + ca-app-pub-3940256099942544~1458002511 + + + NSMicrophoneUsageDescription - Audio Recording + This app needs access to microphone. + + + NSLocationWhenInUseUsageDescription - This app needs access to location when open. + This app needs access to location. \ No newline at end of file diff --git a/client/lib/main.dart b/client/lib/main.dart index 69b68dce4..c39536bd4 100644 --- a/client/lib/main.dart +++ b/client/lib/main.dart @@ -7,6 +7,7 @@ import 'package:flet_audio/flet_audio.dart' as flet_audio; // --FAT_CLIENT_END-- import 'package:flet_audio_recorder/flet_audio_recorder.dart' as flet_audio_recorder; +import 'package:flet_datatable2/flet_datatable2.dart' as flet_datatable2; import "package:flet_flashlight/flet_flashlight.dart" as flet_flashlight; import 'package:flet_geolocator/flet_geolocator.dart' as flet_geolocator; import 'package:flet_lottie/flet_lottie.dart' as flet_lottie; @@ -18,42 +19,52 @@ import 'package:flet_rive/flet_rive.dart' as flet_rive; import 'package:flet_video/flet_video.dart' as flet_video; // --FAT_CLIENT_END-- import 'package:flet_webview/flet_webview.dart' as flet_webview; +import 'package:flet_charts/flet_charts.dart' as flet_charts; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:url_strategy/url_strategy.dart'; +import 'package:flutter_web_plugins/url_strategy.dart'; const bool isProduction = bool.fromEnvironment('dart.vm.product'); void main([List? args]) async { - if (isProduction) { - // ignore: avoid_returning_null_for_void - debugPrint = (String? message, {int? wrapWidth}) => null; - } + // if (isProduction) { + // // ignore: avoid_returning_null_for_void + // debugPrint = (String? message, {int? wrapWidth}) => null; + // } await setupDesktop(); WidgetsFlutterBinding.ensureInitialized(); + List extensions = [ + flet_audio_recorder.Extension(), + flet_geolocator.Extension(), + flet_permission_handler.Extension(), + flet_lottie.Extension(), + flet_map.Extension(), + flet_ads.Extension(), + flet_rive.Extension(), + flet_webview.Extension(), + flet_flashlight.Extension(), + flet_datatable2.Extension(), + flet_charts.Extension(), + ]; // --FAT_CLIENT_START-- - flet_audio.ensureInitialized(); - flet_video.ensureInitialized(); + extensions.add(flet_audio.Extension()); + extensions.add(flet_video.Extension()); // --FAT_CLIENT_END-- - flet_audio_recorder.ensureInitialized(); - flet_geolocator.ensureInitialized(); - flet_permission_handler.ensureInitialized(); - flet_lottie.ensureInitialized(); - flet_map.ensureInitialized(); - flet_ads.ensureInitialized(); - flet_rive.ensureInitialized(); - flet_webview.ensureInitialized(); - flet_flashlight.ensureInitialized(); + + // initialize extensions + for (var extension in extensions) { + extension.ensureInitialized(); + } var pageUrl = Uri.base.toString(); var assetsDir = ""; //debugPrint("Uri.base: ${Uri.base}"); if (kDebugMode) { - pageUrl = "http://localhost:8550"; + pageUrl = "tcp://localhost:8550"; } if (kIsWeb) { @@ -61,12 +72,12 @@ void main([List? args]) async { var routeUrlStrategy = getFletRouteUrlStrategy(); debugPrint("URL Strategy: $routeUrlStrategy"); if (routeUrlStrategy == "path") { - setPathUrlStrategy(); + usePathUrlStrategy(); } } else if ((Platform.isWindows || Platform.isMacOS || Platform.isLinux) && !kDebugMode) { debugPrint("Flet View is running in Desktop mode"); - // first argument must exist + // first argument must be if (args!.isEmpty) { throw Exception('Page URL must be provided as a first argument.'); } @@ -98,26 +109,21 @@ void main([List? args]) async { }; } - runApp(FletApp( + var app = FletApp( title: 'Flet', pageUrl: pageUrl, assetsDir: assetsDir, errorsHandler: errorsHandler, showAppStartupScreen: true, - createControlFactories: [ -// --FAT_CLIENT_START-- - flet_audio.createControl, - flet_video.createControl, -// --FAT_CLIENT_END-- - flet_audio_recorder.createControl, - flet_geolocator.createControl, - flet_permission_handler.createControl, - flet_lottie.createControl, - flet_map.createControl, - flet_ads.createControl, - flet_rive.createControl, - flet_webview.createControl, - flet_flashlight.createControl, - ], - )); + appStartupScreenMessage: "Working...", + extensions: extensions, + multiView: isMultiView(), + ); + + if (app.multiView) { + debugPrint("Flet Web Multi-View mode"); + runWidget(app); + } else { + runApp(app); + } } diff --git a/client/linux/flutter/generated_plugin_registrant.cc b/client/linux/flutter/generated_plugin_registrant.cc index ce73c487f..64afe36fe 100644 --- a/client/linux/flutter/generated_plugin_registrant.cc +++ b/client/linux/flutter/generated_plugin_registrant.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); + g_autoptr(FlPluginRegistrar) volume_controller_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "VolumeControllerPlugin"); + volume_controller_plugin_register_with_registrar(volume_controller_registrar); g_autoptr(FlPluginRegistrar) window_manager_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); window_manager_plugin_register_with_registrar(window_manager_registrar); diff --git a/client/linux/flutter/generated_plugins.cmake b/client/linux/flutter/generated_plugins.cmake index db550dddc..71b63382d 100644 --- a/client/linux/flutter/generated_plugins.cmake +++ b/client/linux/flutter/generated_plugins.cmake @@ -10,12 +10,12 @@ list(APPEND FLUTTER_PLUGIN_LIST rive_common screen_retriever_linux url_launcher_linux + volume_controller window_manager window_to_front ) list(APPEND FLUTTER_FFI_PLUGIN_LIST - media_kit_native_event_loop ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/client/macos/Flutter/GeneratedPluginRegistrant.swift b/client/macos/Flutter/GeneratedPluginRegistrant.swift index ab5ba1dcd..18f174849 100644 --- a/client/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/client/macos/Flutter/GeneratedPluginRegistrant.swift @@ -13,12 +13,12 @@ import media_kit_libs_macos_video import media_kit_video import package_info_plus import path_provider_foundation -import record_darwin +import record_macos import rive_common -import screen_brightness_macos import screen_retriever_macos import shared_preferences_foundation import url_launcher_macos +import volume_controller import wakelock_plus import webview_flutter_wkwebview import window_manager @@ -33,14 +33,14 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) - RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin")) + RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin")) RivePlugin.register(with: registry.registrar(forPlugin: "RivePlugin")) - ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + VolumeControllerPlugin.register(with: registry.registrar(forPlugin: "VolumeControllerPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) - FLTWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "FLTWebViewFlutterPlugin")) + WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin")) } diff --git a/client/macos/Runner/Info.plist b/client/macos/Runner/Info.plist index 4ceb1fd1f..6fd83ba45 100644 --- a/client/macos/Runner/Info.plist +++ b/client/macos/Runner/Info.plist @@ -30,9 +30,13 @@ MainMenu NSPrincipalClass NSApplication + + NSMicrophoneUsageDescription - Audio Recording - NSLocationUsageDescription - This app needs access to location. + This app needs access to microphone. + + + NSLocationWhenInUseUsageDescription + This app needs access to location. \ No newline at end of file diff --git a/client/pubspec.lock b/client/pubspec.lock index 4a46277cf..438cebee6 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -13,74 +13,74 @@ packages: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.7.0" async: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" audioplayers: dependency: transitive description: name: audioplayers - sha256: c05c6147124cd63e725e861335a8b4d57300b80e6e92cea7c145c739223bbaef + sha256: e653f162ddfcec1da2040ba2d8553fff1662b5c2a5c636f4c21a3b11bee497de url: "https://pub.dev" source: hosted - version: "5.2.1" + version: "6.5.0" audioplayers_android: dependency: transitive description: name: audioplayers_android - sha256: b00e1a0e11365d88576320ec2d8c192bc21f1afb6c0e5995d1c57ae63156acb5 + sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605" url: "https://pub.dev" source: hosted - version: "4.0.3" + version: "5.2.1" audioplayers_darwin: dependency: transitive description: name: audioplayers_darwin - sha256: "3034e99a6df8d101da0f5082dcca0a2a99db62ab1d4ddb3277bed3f6f81afe08" + sha256: "0811d6924904ca13f9ef90d19081e4a87f7297ddc19fc3d31f60af1aaafee333" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "6.3.0" audioplayers_linux: dependency: transitive description: name: audioplayers_linux - sha256: "60787e73fefc4d2e0b9c02c69885402177e818e4e27ef087074cf27c02246c9e" + sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.2.1" audioplayers_platform_interface: dependency: transitive description: name: audioplayers_platform_interface - sha256: "365c547f1bb9e77d94dd1687903a668d8f7ac3409e48e6e6a3668a1ac2982adb" + sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "7.1.1" audioplayers_web: dependency: transitive description: name: audioplayers_web - sha256: "22cd0173e54d92bd9b2c80b1204eb1eb159ece87475ab58c9788a70ec43c2a62" + sha256: "1c0f17cec68455556775f1e50ca85c40c05c714a99c5eb1d2d57cc17ba5522d7" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "5.1.1" audioplayers_windows: dependency: transitive description: name: audioplayers_windows - sha256: "9536812c9103563644ada2ef45ae523806b0745f7a78e89d1b5fb1951de90e1a" + sha256: "4048797865105b26d47628e6abb49231ea5de84884160229251f37dfcbe52fd7" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.2.1" boolean_selector: dependency: transitive description: @@ -101,18 +101,18 @@ packages: dependency: transitive description: name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" cli_util: dependency: transitive description: name: cli_util - sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c url: "https://pub.dev" source: hosted - version: "0.4.1" + version: "0.4.2" clock: dependency: transitive description: @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.6" cupertino_icons: dependency: "direct main" description: @@ -157,74 +157,82 @@ packages: dependency: transitive description: name: dart_earcut - sha256: "41b493147e30a051efb2da1e3acb7f38fe0db60afba24ac1ea5684cee272721e" + sha256: e485001bfc05dcbc437d7bfb666316182e3522d4c3f9668048e004d0eb2ce43b url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" + data_table_2: + dependency: transitive + description: + name: data_table_2 + sha256: b8dd157e4efe5f2beef092c9952a254b2192cf76a26ad1c6aa8b06c8b9d665da + url: "https://pub.dev" + source: hosted + version: "2.6.0" dbus: dependency: transitive description: name: dbus - sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" device_info_plus: dependency: transitive description: name: device_info_plus - sha256: b37d37c2f912ad4e8ec694187de87d05de2a3cb82b465ff1f65f65a2d05de544 + sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" url: "https://pub.dev" source: hosted - version: "11.2.1" + version: "11.5.0" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f url: "https://pub.dev" source: hosted - version: "7.0.2" + version: "7.0.3" dio: dependency: transitive description: name: dio - sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" url: "https://pub.dev" source: hosted - version: "5.7.0" + version: "5.8.0+1" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.1" equatable: dependency: transitive description: name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.0.7" fake_async: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" file: dependency: transitive description: @@ -245,67 +253,85 @@ packages: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" fl_chart: dependency: transitive description: name: fl_chart - sha256: "94307bef3a324a0d329d3ab77b2f0c6e5ed739185ffc029ed28c0f9b019ea7ef" + sha256: "577aeac8ca414c25333334d7c4bb246775234c0e44b38b10a82b559dd4d764e7" url: "https://pub.dev" source: hosted - version: "0.69.0" + version: "1.0.0" flet: dependency: "direct overridden" description: path: "../packages/flet" relative: true source: path - version: "0.28.3" + version: "0.70.0" flet_ads: dependency: "direct main" description: path: "src/flutter/flet_ads" - ref: "0.1.0" - resolved-ref: "01e7065fcbebb1317b3f16951e6cf0c5fcff658b" + ref: v1 + resolved-ref: d52c10df46113a73669f0897cff1cc51f45c4df7 url: "https://github.com/flet-dev/flet-ads.git" source: git - version: "0.1.0" + version: "0.2.0" flet_audio: dependency: "direct main" description: path: "src/flutter/flet_audio" - ref: "0.1.0" - resolved-ref: a146bf580a211c6d1f5c45070d15532ea2e92af7 + ref: v1 + resolved-ref: "35cffd36eb481ac4ac361d86a2d9ec1d8833804f" url: "https://github.com/flet-dev/flet-audio.git" source: git - version: "0.1.0" + version: "0.2.0" flet_audio_recorder: dependency: "direct main" description: path: "src/flutter/flet_audio_recorder" - ref: "0.1.0" - resolved-ref: eb186cd49fde3136f65d0d074bca5ca688260e53 + ref: v1 + resolved-ref: "776e614815a581a49233fa4d799953d9f0279277" url: "https://github.com/flet-dev/flet-audio-recorder.git" source: git + version: "0.2.0" + flet_charts: + dependency: "direct main" + description: + path: "src/flutter/flet_charts" + ref: v1 + resolved-ref: cb3c7b56ea13e078526190b07be9bc113ce10ace + url: "https://github.com/flet-dev/flet-charts.git" + source: git + version: "0.1.0" + flet_datatable2: + dependency: "direct main" + description: + path: "src/flutter/flet_datatable2" + ref: v1 + resolved-ref: "98c2b38801ddc131e30d2082b4fc9271201384a8" + url: "https://github.com/flet-dev/flet-datatable2.git" + source: git version: "0.1.0" flet_flashlight: dependency: "direct main" description: path: "src/flutter/flet_flashlight" - ref: "0.1.0" - resolved-ref: a12a12259eaac801ee99d465727ec4a9fe50121e + ref: v1 + resolved-ref: ed8be1e10886a8e1cfffe444bff8c82999908125 url: "https://github.com/flet-dev/flet-flashlight.git" source: git - version: "0.1.0" + version: "0.2.0" flet_geolocator: dependency: "direct main" description: path: "src/flutter/flet_geolocator" - ref: "0.1.0" - resolved-ref: "7b17d7aab169a7488da90fd73b3acd8e568f4f03" + ref: v1 + resolved-ref: "9f7c54c0a66a910a3ba3e325b3fef132c930015a" url: "https://github.com/flet-dev/flet-geolocator.git" source: git version: "0.25.2" @@ -313,56 +339,56 @@ packages: dependency: "direct main" description: path: "src/flutter/flet_lottie" - ref: "0.1.0" - resolved-ref: f28c767b5d43de650155a5a97e2d971fb833c9f8 + ref: v1 + resolved-ref: "46f622d6af9b62a7938f1bc87297fde756535c49" url: "https://github.com/flet-dev/flet-lottie.git" source: git - version: "0.1.0" + version: "0.2.0" flet_map: dependency: "direct main" description: path: "src/flutter/flet_map" - ref: "0.1.0" - resolved-ref: dfa3cf6776b0dc5f238227d5f378cda7be190eaf + ref: v1 + resolved-ref: "163d73f4faa871acda0d2de20e454143ed4108d0" url: "https://github.com/flet-dev/flet-map.git" source: git - version: "0.1.0" + version: "0.2.0" flet_permission_handler: dependency: "direct main" description: path: "src/flutter/flet_permission_handler" - ref: "0.1.0" - resolved-ref: "4ead86b6c7e54b914215b6087fcb17b7e921d75c" + ref: v1 + resolved-ref: "64e95b8eb66b516935383c1365e0d8470acfcc96" url: "https://github.com/flet-dev/flet-permission-handler.git" source: git - version: "0.1.0" + version: "0.2.0" flet_rive: dependency: "direct main" description: path: "src/flutter/flet_rive" - ref: "0.1.0" - resolved-ref: "089f7ad923f747ad764aa2781eca9a36382bb912" + ref: v1 + resolved-ref: d62927ad51f16c39cd6592bad34e983aee0f4d61 url: "https://github.com/flet-dev/flet-rive.git" source: git - version: "0.1.0" + version: "0.2.0" flet_video: dependency: "direct main" description: path: "src/flutter/flet_video" - ref: "0.1.0" - resolved-ref: "76f3f76f04c7573e047204648378ee2f31db7a15" + ref: v1 + resolved-ref: "7c05efe78f0409b5b57dcfad13fa1ecafeb5788b" url: "https://github.com/flet-dev/flet-video.git" source: git - version: "0.1.0" + version: "0.2.0" flet_webview: dependency: "direct main" description: path: "src/flutter/flet_webview" - ref: "0.1.0" - resolved-ref: b1cfb3155469bc0a9a7578afcd58e7f32669b164 + ref: v1 + resolved-ref: "8d592b0485e4c137fdd5eae14f9395bb75df5984" url: "https://github.com/flet-dev/flet-webview.git" source: git - version: "0.1.0" + version: "0.2.0" flutter: dependency: "direct main" description: flutter @@ -406,65 +432,57 @@ packages: dependency: transitive description: name: flutter_map - sha256: "2ecb34619a4be19df6f40c2f8dce1591675b4eff7a6857bd8f533706977385da" + sha256: f7d0379477274f323c3f3bc12d369a2b42eb86d1e7bd2970ae1ea3cff782449a url: "https://pub.dev" source: hosted - version: "7.0.2" + version: "8.1.1" flutter_map_animations: dependency: transitive description: name: flutter_map_animations - sha256: "08233f89919049a3601e785d32e9d1d9e1faac6578190150f1d7495fc1050d36" + sha256: bf583863561861aaaf4854ae7ed8940d79bea7d32918bf7a85d309b25235a09e url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.9.0" flutter_map_cancellable_tile_provider: dependency: transitive description: name: flutter_map_cancellable_tile_provider - sha256: "03662220ce0cd784ad2f2a45c36fc379b8b315c74f5c12b5ff4a0515eab1acd1" + sha256: "801760c104a3cfd9268cda7c9b1241223247e8182613a7e060ef4ffc0d825ac8" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.1.0" flutter_markdown: dependency: transitive description: name: flutter_markdown - sha256: f0e599ba89c9946c8e051780f0ec99aba4ba15895e0380a7ab68f420046fc44e + sha256: e7bbc718adc9476aa14cfddc1ef048d2e21e4e8f18311aaac723266db9f9e7b5 url: "https://pub.dev" source: hosted - version: "0.7.4+1" + version: "0.7.6+2" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" - url: "https://pub.dev" - source: hosted - version: "2.0.24" - flutter_redux: - dependency: transitive - description: - name: flutter_redux - sha256: "3b20be9e08d0038e1452fbfa1fdb1ea0a7c3738c997734530b3c6d0bb5fcdbdc" + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e url: "https://pub.dev" source: hosted - version: "0.10.0" + version: "2.0.28" flutter_svg: dependency: transitive description: name: flutter_svg - sha256: de82e6bf958cec7190fbc1c5298282c851228e35ae2b14e2b103e7f777818c64 + sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1 url: "https://pub.dev" source: hosted - version: "2.0.13" + version: "2.1.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" flutter_web_plugins: - dependency: transitive + dependency: "direct main" description: flutter source: sdk version: "0.0.0" @@ -477,58 +495,58 @@ packages: dependency: transitive description: name: geolocator - sha256: "0ec58b731776bc43097fcf751f79681b6a8f6d3bc737c94779fe9f1ad73c1a81" + sha256: ee2212a3df8292ec4c90b91183b8001d3f5a800823c974b570c5f9344ca320dc url: "https://pub.dev" source: hosted - version: "13.0.1" + version: "14.0.1" geolocator_android: dependency: transitive description: name: geolocator_android - sha256: "7aefc530db47d90d0580b552df3242440a10fe60814496a979aa67aa98b1fd47" + sha256: "114072db5d1dce0ec0b36af2697f55c133bc89a2c8dd513e137c0afe59696ed4" url: "https://pub.dev" source: hosted - version: "4.6.1" + version: "5.0.1+1" geolocator_apple: dependency: transitive description: name: geolocator_apple - sha256: bc2aca02423ad429cb0556121f56e60360a2b7d694c8570301d06ea0c00732fd + sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22 url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "2.3.13" geolocator_platform_interface: dependency: transitive description: name: geolocator_platform_interface - sha256: "386ce3d9cce47838355000070b1d0b13efb5bc430f8ecda7e9238c8409ace012" + sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67" url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "4.2.6" geolocator_web: dependency: transitive description: name: geolocator_web - sha256: "2ed69328e05cd94e7eb48bb0535f5fc0c0c44d1c4fa1e9737267484d05c29b5e" + sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172 url: "https://pub.dev" source: hosted - version: "4.1.1" + version: "4.1.3" geolocator_windows: dependency: transitive description: name: geolocator_windows - sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e" + sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6" url: "https://pub.dev" source: hosted - version: "0.2.3" + version: "0.2.5" google_mobile_ads: dependency: transitive description: name: google_mobile_ads - sha256: "4775006383a27a5d86d46f8fb452bfcb17794fc0a46c732979e49a8eb1c8963f" + sha256: a4f59019f2c32769fb6c60ed8aa321e9c21a36297e2c4f23452b3e779a3e7a26 url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "6.0.0" graphs: dependency: transitive description: @@ -549,26 +567,26 @@ packages: dependency: transitive description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.0" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" image: dependency: transitive description: name: image - sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" + sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.3.0" integration_test: dependency: "direct main" description: flutter @@ -578,18 +596,10 @@ packages: dependency: transitive description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf - url: "https://pub.dev" - source: hosted - version: "0.19.0" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.20.2" json_annotation: dependency: transitive description: @@ -610,10 +620,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -650,34 +660,34 @@ packages: dependency: transitive description: name: logger - sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32" + sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1 url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.5.0" logging: dependency: transitive description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" lottie: dependency: transitive description: name: lottie - sha256: "6a24ade5d3d918c306bb1c21a6b9a04aab0489d51a2582522eea820b4093b62b" + sha256: "377d87b8dcef640c04717e93afb86a510f0e1117a399ab94dc4b3f39c85eaa87" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.3.0" markdown: dependency: transitive description: name: markdown - sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051 + sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" url: "https://pub.dev" source: hosted - version: "7.2.2" + version: "7.3.0" matcher: dependency: transitive description: @@ -698,18 +708,18 @@ packages: dependency: transitive description: name: media_kit - sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a" + sha256: "48c10c3785df5d88f0eef970743f8c99b2e5da2b34b9d8f9876e598f62d9e776" url: "https://pub.dev" source: hosted - version: "1.1.10+1" + version: "1.2.0" media_kit_libs_android_video: dependency: transitive description: name: media_kit_libs_android_video - sha256: "9dd8012572e4aff47516e55f2597998f0a378e3d588d0fad0ca1f11a53ae090c" + sha256: adff9b571b8ead0867f9f91070f8df39562078c0eb3371d88b9029a2d547d7b7 url: "https://pub.dev" source: hosted - version: "1.3.6" + version: "1.3.7" media_kit_libs_ios_video: dependency: transitive description: @@ -722,10 +732,10 @@ packages: dependency: transitive description: name: media_kit_libs_linux - sha256: e186891c31daa6bedab4d74dcdb4e8adfccc7d786bfed6ad81fe24a3b3010310 + sha256: "2b473399a49ec94452c4d4ae51cfc0f6585074398d74216092bf3d54aac37ecf" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.2.1" media_kit_libs_macos_video: dependency: transitive description: @@ -738,34 +748,26 @@ packages: dependency: transitive description: name: media_kit_libs_video - sha256: "3688e0c31482074578652bf038ce6301a5d21e1eda6b54fc3117ffeb4bdba067" + sha256: "958cc55e7065d9d01f52a2842dab2a0812a92add18489f1006d864fb5e42a3ef" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.6" media_kit_libs_windows_video: dependency: transitive description: name: media_kit_libs_windows_video - sha256: "7bace5f35d9afcc7f9b5cdadb7541d2191a66bb3fc71bfa11c1395b3360f6122" + sha256: dff76da2778729ab650229e6b4ec6ec111eb5151431002cbd7ea304ff1f112ab url: "https://pub.dev" source: hosted - version: "1.0.9" - media_kit_native_event_loop: - dependency: transitive - description: - name: media_kit_native_event_loop - sha256: a605cf185499d14d58935b8784955a92a4bf0ff4e19a23de3d17a9106303930e - url: "https://pub.dev" - source: hosted - version: "1.0.8" + version: "1.0.11" media_kit_video: dependency: transitive description: name: media_kit_video - sha256: c048d11a19e379aebbe810647636e3fc6d18374637e2ae12def4ff8a4b99a882 + sha256: a656a9463298c1adc64c57f2d012874f7f2900f0c614d9545a3e7b8bb9e2137b url: "https://pub.dev" source: hosted - version: "1.2.4" + version: "1.3.0" meta: dependency: transitive description: @@ -782,22 +784,38 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + msgpack_dart: + dependency: transitive + description: + name: msgpack_dart + sha256: c2d235ed01f364719b5296aecf43ac330f0d7bc865fa134d0d7910a40454dffb + url: "https://pub.dev" + source: hosted + version: "1.0.1" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" package_info_plus: dependency: transitive description: name: package_info_plus - sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" url: "https://pub.dev" source: hosted - version: "8.0.2" + version: "8.3.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.2.0" path: dependency: transitive description: @@ -810,34 +828,34 @@ packages: dependency: transitive description: name: path_parsing - sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" path_provider: dependency: transitive description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb" + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.dev" source: hosted - version: "2.2.9" + version: "2.2.17" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -866,42 +884,42 @@ packages: dependency: transitive description: name: permission_handler - sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" + sha256: "2d070d8684b68efb580a5997eb62f675e8a885ef0be6e754fb9ef489c177470f" url: "https://pub.dev" source: hosted - version: "11.3.1" + version: "12.0.0+1" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: eaf2a1ec4472775451e88ca6a7b86559ef2f1d1ed903942ed135e38ea0097dca + sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" url: "https://pub.dev" source: hosted - version: "12.0.8" + version: "13.0.1" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0 + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 url: "https://pub.dev" source: hosted - version: "9.4.5" + version: "9.4.7" permission_handler_html: dependency: transitive description: name: permission_handler_html - sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851 + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" url: "https://pub.dev" source: hosted - version: "0.1.3+2" + version: "0.1.3+5" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: fe0ffe274d665be8e34f9c59705441a7d248edebbe5d9e3ec2665f88b79358ea + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.3.0" permission_handler_windows: dependency: transitive description: @@ -914,10 +932,10 @@ packages: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.1.0" platform: dependency: transitive description: @@ -958,70 +976,78 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + provider: + dependency: transitive + description: + name: provider + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" + url: "https://pub.dev" + source: hosted + version: "6.1.5" record: dependency: transitive description: name: record - sha256: "4a5cf4d083d1ee49e0878823c4397d073f8eb0a775f31215d388e2bc47a9e867" + sha256: daeb3f9b3fea9797094433fe6e49a879d8e4ca4207740bc6dc7e4a58764f0817 url: "https://pub.dev" source: hosted - version: "5.1.2" + version: "6.0.0" record_android: dependency: transitive description: name: record_android - sha256: "9ccf6a206dc72b486cf37893690e70c17610e8f05dba8da1a808e73dc2f49a04" + sha256: "97d7122455f30de89a01c6c244c839085be6b12abca251fc0e78f67fed73628b" url: "https://pub.dev" source: hosted - version: "1.2.4" - record_darwin: + version: "1.3.3" + record_ios: dependency: transitive description: - name: record_darwin - sha256: b038c26d1066eb81f4e7433bfb85f0d450ca3fac0002a7216b83a21b775ecf21 + name: record_ios + sha256: "73706ebbece6150654c9d6f57897cf9b622c581148304132ba85dba15df0fdfb" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.0.0" record_linux: dependency: transitive description: name: record_linux - sha256: "74d41a9ebb1eb498a38e9a813dd524e8f0b4fdd627270bda9756f437b110a3e3" + sha256: "29e7735b05c1944bb6c9b72a36c08d4a1b24117e712d6a9523c003bde12bf484" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + record_macos: + dependency: transitive + description: + name: record_macos + sha256: "02240833fde16c33fcf2c589f3e08d4394b704761b4a3bb609d872ff3043fbbd" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "1.0.0" record_platform_interface: dependency: transitive description: name: record_platform_interface - sha256: "11f8b03ea8a0e279b0e306571dbe0db0202c0b8e866495c9fa1ad2281d5e4c15" + sha256: "8a575828733d4c3cb5983c914696f40db8667eab3538d4c41c50cbb79e722ef4" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" record_web: dependency: transitive description: name: record_web - sha256: "656b7a865f90651fab997c2a563364f5fd60a0b527d5dadbb915d62d84fc3867" + sha256: "024c81eb7f51468b1833a3eca8b461c7ca25c04899dba37abe580bb57afd32e4" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.1.8" record_windows: dependency: transitive description: name: record_windows - sha256: e653555aa3fda168aded7c34e11bd82baf0c6ac84e7624553def3c77ffefd36f + sha256: "85a22fc97f6d73ecd67c8ba5f2f472b74ef1d906f795b7970f771a0914167e99" url: "https://pub.dev" source: hosted - version: "1.0.3" - redux: - dependency: transitive - description: - name: redux - sha256: "1e86ed5b1a9a717922d0a0ca41f9bf49c1a587d50050e9426fc65b14e85ec4d7" - url: "https://pub.dev" - source: hosted - version: "5.0.0" + version: "1.0.6" rive: dependency: transitive description: @@ -1042,58 +1068,26 @@ packages: dependency: transitive description: name: safe_local_storage - sha256: ede4eb6cb7d88a116b3d3bf1df70790b9e2038bc37cb19112e381217c74d9440 - url: "https://pub.dev" - source: hosted - version: "1.0.2" - screen_brightness: - dependency: transitive - description: - name: screen_brightness - sha256: ed8da4a4511e79422fc1aa88138e920e4008cd312b72cdaa15ccb426c0faaedd + sha256: e9a21b6fec7a8aa62cc2585ff4c1b127df42f3185adbd2aca66b47abe2e80236 url: "https://pub.dev" source: hosted - version: "0.2.2+1" + version: "2.0.1" screen_brightness_android: dependency: transitive description: name: screen_brightness_android - sha256: "3df10961e3a9e968a5e076fe27e7f4741fa8a1d3950bdeb48cf121ed529d0caf" - url: "https://pub.dev" - source: hosted - version: "0.1.0+2" - screen_brightness_ios: - dependency: transitive - description: - name: screen_brightness_ios - sha256: "99adc3ca5490b8294284aad5fcc87f061ad685050e03cf45d3d018fe398fd9a2" - url: "https://pub.dev" - source: hosted - version: "0.1.0" - screen_brightness_macos: - dependency: transitive - description: - name: screen_brightness_macos - sha256: "64b34e7e3f4900d7687c8e8fb514246845a73ecec05ab53483ed025bd4a899fd" + sha256: "6ba1b5812f66c64e9e4892be2d36ecd34210f4e0da8bdec6a2ea34f1aa42683e" url: "https://pub.dev" source: hosted - version: "0.1.0+1" + version: "2.1.1" screen_brightness_platform_interface: dependency: transitive description: name: screen_brightness_platform_interface - sha256: b211d07f0c96637a15fb06f6168617e18030d5d74ad03795dd8547a52717c171 + sha256: "737bd47b57746bc4291cab1b8a5843ee881af499514881b0247ec77447ee769c" url: "https://pub.dev" source: hosted - version: "0.1.0" - screen_brightness_windows: - dependency: transitive - description: - name: screen_brightness_windows - sha256: "9261bf33d0fc2707d8cf16339ce25768100a65e70af0fcabaf032fc12408ba86" - url: "https://pub.dev" - source: hosted - version: "0.1.3" + version: "2.1.0" screen_retriever: dependency: transitive description: @@ -1138,50 +1132,50 @@ packages: dependency: transitive description: name: sensors_plus - sha256: "8e7fa79b4940442bb595bfc0ee9da4af5a22a0fe6ebacc74998245ee9496a82d" + sha256: "905282c917c6bb731c242f928665c2ea15445aa491249dea9d98d7c79dc8fd39" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "6.1.1" sensors_plus_platform_interface: dependency: transitive description: name: sensors_plus_platform_interface - sha256: bc472d6cfd622acb4f020e726433ee31788b038056691ba433fec80e448a094f + sha256: "58815d2f5e46c0c41c40fb39375d3f127306f7742efe3b891c0b1c87e2b5cd5d" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "2.0.1" shared_preferences: dependency: transitive description: name: shared_preferences - sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.5.2" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "041be4d9d2dc6079cf342bc8b761b03787e3b71192d658220a56cac9c04a0294" + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.10" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833" + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1" + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: @@ -1194,18 +1188,18 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "59dc807b94d29d52ddbb1b3c0d3b9d0a67fc535a64e62a5542c8db0513fcb6c2" + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.3" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2" + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" sky_engine: dependency: transitive description: flutter @@ -1263,10 +1257,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" url: "https://pub.dev" source: hosted - version: "3.1.0+1" + version: "3.3.1" term_glyph: dependency: transitive description: @@ -1295,10 +1289,10 @@ packages: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" unicode: dependency: transitive description: @@ -1319,10 +1313,10 @@ packages: dependency: transitive description: name: uri_parser - sha256: "6543c9fd86d2862fac55d800a43e67c0dcd1a41677cb69c2f8edfe73bbcf1835" + sha256: ff4d2c720aca3f4f7d5445e23b11b2d15ef8af5ddce5164643f38ff962dcb270 url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "3.0.0" url_launcher: dependency: transitive description: @@ -1335,34 +1329,34 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "94d8ad05f44c6d4e2ffe5567ab4d741b82d62e3c8e288cc1fcea45965edf47c9" + sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" url: "https://pub.dev" source: hosted - version: "6.3.8" + version: "6.3.16" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.3" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -1375,58 +1369,50 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.1" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" - url: "https://pub.dev" - source: hosted - version: "3.1.2" - url_strategy: - dependency: "direct main" - description: - name: url_strategy - sha256: "42b68b42a9864c4d710401add17ad06e28f1c1d5500c93b98c431f6b0ea4ab87" + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "3.1.4" uuid: dependency: transitive description: name: uuid - sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff url: "https://pub.dev" source: hosted - version: "4.4.2" + version: "4.5.1" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.19" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.13" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + sha256: "557a315b7d2a6dbb0aaaff84d857967ce6bdc96a63dc6ee2a57ce5a6ee5d3331" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.17" vector_math: dependency: transitive description: @@ -1439,122 +1425,130 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" volume_controller: dependency: transitive description: name: volume_controller - sha256: "189bdc7a554f476b412e4c8b2f474562b09d74bc458c23667356bce3ca1d48c9" + sha256: d75039e69c0d90e7810bfd47e3eedf29ff8543ea7a10392792e81f9bded7edf5 url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "3.4.0" wakelock_plus: dependency: transitive description: name: wakelock_plus - sha256: bf4ee6f17a2fa373ed3753ad0e602b7603f8c75af006d5b9bdade263928c0484 + sha256: a474e314c3e8fb5adef1f9ae2d247e57467ad557fa7483a2b895bc1b421c5678 url: "https://pub.dev" source: hosted - version: "1.2.8" + version: "1.3.2" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "422d1cdbb448079a8a62a5a770b69baa489f8f7ca21aef47800c726d404f9d16" + sha256: e10444072e50dbc4999d7316fd303f7ea53d31c824aa5eb05d7ccbdd98985207 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.3" web: dependency: transitive description: name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.3" webdriver: dependency: transitive description: name: webdriver - sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" + sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.1.0" webview_flutter: dependency: transitive description: name: webview_flutter - sha256: "889a0a678e7c793c308c68739996227c9661590605e70b1f6cf6b9a6634f7aec" + sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba url: "https://pub.dev" source: hosted - version: "4.10.0" + version: "4.13.0" webview_flutter_android: - dependency: "direct overridden" + dependency: transitive description: name: webview_flutter_android - sha256: "74693a212d990b32e0b7055d27db973a18abf31c53942063948cdfaaef9787ba" + sha256: f6e6afef6e234801da77170f7a1847ded8450778caf2fe13979d140484be3678 url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.7.0" webview_flutter_platform_interface: dependency: transitive description: name: webview_flutter_platform_interface - sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d + sha256: f0dc2dc3a2b1e3a6abdd6801b9355ebfeb3b8f6cde6b9dc7c9235909c4a1f147 url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.13.1" webview_flutter_web: dependency: transitive description: name: webview_flutter_web - sha256: cbe1efe45e1be8470fdef7ddb75e2e2998c7ca47b75c09b9354934d20eca146b + sha256: "18a7ccc1c31dd9a5c759a1b7217a2a1e04bd8f65712714a4070bfac19a23ca9e" url: "https://pub.dev" source: hosted - version: "0.2.3+2" + version: "0.2.3+4" webview_flutter_wkwebview: dependency: transitive description: name: webview_flutter_wkwebview - sha256: "1942a12224ab31e9508cf00c0c6347b931b023b8a4f0811e5dec3b06f94f117d" + sha256: a3d461fe3467014e05f3ac4962e5fdde2a4bf44c561cb53e9ae5c586600fdbc3 url: "https://pub.dev" source: hosted - version: "3.15.0" + version: "3.22.0" win32: dependency: transitive description: name: win32 - sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" url: "https://pub.dev" source: hosted - version: "5.10.1" + version: "5.14.0" win32_registry: dependency: transitive description: name: win32_registry - sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6" + sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "2.1.0" window_manager: dependency: transitive description: name: window_manager - sha256: "732896e1416297c63c9e3fb95aea72d0355f61390263982a47fd519169dc5059" + sha256: "51d50168ab267d344b975b15390426b1243600d436770d3f13de67e55b05ec16" url: "https://pub.dev" source: hosted - version: "0.4.3" + version: "0.5.0" window_to_front: dependency: transitive description: @@ -1575,10 +1569,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" xml: dependency: transitive description: @@ -1591,10 +1585,10 @@ packages: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" sdks: - dart: ">=3.7.0-0 <4.0.0" - flutter: ">=3.24.0" + dart: ">=3.8.0 <4.0.0" + flutter: ">=3.29.0" diff --git a/client/pubspec.yaml b/client/pubspec.yaml index c6f003aba..9a0cc0734 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -27,83 +27,94 @@ environment: # the latest version available on pub.dev. To see which dependencies have newer # versions available, run `flutter pub outdated`. dependencies: - flutter: sdk: flutter + flutter_web_plugins: + sdk: flutter # --FAT_CLIENT_START-- flet_audio: git: url: https://github.com/flet-dev/flet-audio.git - ref: 0.1.0 + ref: v1 path: src/flutter/flet_audio flet_video: git: url: https://github.com/flet-dev/flet-video.git - ref: 0.1.0 + ref: v1 path: src/flutter/flet_video # --FAT_CLIENT_END-- flet_lottie: git: url: https://github.com/flet-dev/flet-lottie.git - ref: 0.1.0 + ref: v1 path: src/flutter/flet_lottie flet_map: git: url: https://github.com/flet-dev/flet-map.git - ref: 0.1.0 + ref: v1 path: src/flutter/flet_map flet_ads: git: url: https://github.com/flet-dev/flet-ads.git - ref: 0.1.0 + ref: v1 path: src/flutter/flet_ads flet_rive: git: url: https://github.com/flet-dev/flet-rive.git - ref: 0.1.0 + ref: v1 path: src/flutter/flet_rive flet_audio_recorder: git: url: https://github.com/flet-dev/flet-audio-recorder.git - ref: 0.1.0 + ref: v1 path: src/flutter/flet_audio_recorder flet_permission_handler: git: url: https://github.com/flet-dev/flet-permission-handler.git - ref: 0.1.0 + ref: v1 path: src/flutter/flet_permission_handler - + flet_geolocator: git: url: https://github.com/flet-dev/flet-geolocator.git - ref: 0.1.0 + ref: v1 path: src/flutter/flet_geolocator - + flet_webview: git: url: https://github.com/flet-dev/flet-webview.git - ref: 0.1.0 + ref: v1 path: src/flutter/flet_webview flet_flashlight: git: url: https://github.com/flet-dev/flet-flashlight.git - ref: 0.1.0 + ref: v1 path: src/flutter/flet_flashlight + flet_datatable2: + git: + url: https://github.com/flet-dev/flet-datatable2.git + ref: v1 + path: src/flutter/flet_datatable2 + + flet_charts: + git: + url: https://github.com/flet-dev/flet-charts.git + ref: v1 + path: src/flutter/flet_charts - url_strategy: ^0.2.0 cupertino_icons: ^1.0.6 - + integration_test: sdk: flutter dependency_overrides: flet: path: ../packages/flet - webview_flutter_android: ^4.0.0 + # webview_flutter_android: ^4.0.0 dev_dependencies: flutter_test: diff --git a/client/web/assets/fonts/roboto.woff2 b/client/web/assets/fonts/roboto.woff2 new file mode 100644 index 000000000..8fa8d2ae9 Binary files /dev/null and b/client/web/assets/fonts/roboto.woff2 differ diff --git a/client/web/flutter_bootstrap.js b/client/web/flutter_bootstrap.js index 01f933bc5..939c14080 100644 --- a/client/web/flutter_bootstrap.js +++ b/client/web/flutter_bootstrap.js @@ -2,22 +2,34 @@ {{flutter_build_config}} var loading = document.querySelector('#loading'); + +var flutterConfig = { + multiViewEnabled: flet.multiView, + assetBase: flet.assetBase +}; +if (flet.webRenderer != "auto") { + flutterConfig.renderer = flet.webRenderer; +} +if (flet.noCdn) { + flutterConfig.canvasKitBaseUrl = flet.canvasKitBaseUrl; + flutterConfig.fontFallbackBaseUrl = flet.fontFallbackBaseUrl; +} + _flutter.loader.load({ - config: { - renderer: webRenderer - }, + config: flutterConfig, serviceWorkerSettings: { serviceWorkerVersion: {{flutter_service_worker_version}}, }, onEntrypointLoaded: async function (engineInitializer) { loading.classList.add('main_done'); - const appRunner = await engineInitializer.initializeEngine({useColorEmoji: useColorEmoji}); + const engine = await engineInitializer.initializeEngine(flutterConfig); loading.classList.add('init_done'); - await appRunner.runApp(); + flet.flutterApp = await engine.runApp(); + flet.flutterAppResolve(flet.flutterApp); window.setTimeout(function () { loading.remove(); }, 200); } -}); \ No newline at end of file +}); diff --git a/client/web/index.html b/client/web/index.html index 92f213416..d840c1ce6 100644 --- a/client/web/index.html +++ b/client/web/index.html @@ -17,23 +17,63 @@ - - - - - Flet - - + - + @@ -95,5 +135,10 @@ Loading... + +

View 1

+
+

View 2

+
- \ No newline at end of file + diff --git a/client/web/python-worker.js b/client/web/python-worker.js index 76049195e..d73b59fd8 100644 --- a/client/web/python-worker.js +++ b/client/web/python-worker.js @@ -1,42 +1,93 @@ -importScripts("https://cdn.jsdelivr.net/pyodide/v0.27.2/full/pyodide.js"); - +self.pyodideUrl = null; +self.appPackageUrl = null; self.micropipIncludePre = false; self.pythonModuleName = null; +self.documentUrl = null; self.initialized = false; self.flet_js = {}; // namespace for Python global functions self.initPyodide = async function () { - self.pyodide = await loadPyodide(); - self.pyodide.registerJsModule("flet_js", flet_js); - flet_js.documentUrl = documentUrl; - await self.pyodide.loadPackage("micropip"); - let pre = self.micropipIncludePre ? "True" : "False"; - await self.pyodide.runPythonAsync(` - import micropip - import os - from pyodide.http import pyfetch - response = await pyfetch("app.tar.gz") - await response.unpack_archive() - if os.path.exists("requirements.txt"): - with open("requirements.txt", "r") as f: - deps = [line.rstrip() for line in f] - print("Loading requirements.txt:", deps) - await micropip.install(deps, pre=${pre}) - `); - pyodide.pyimport(self.pythonModuleName); - await self.flet_js.start_connection(self.receiveCallback); - self.postMessage("initialized"); + try { + importScripts(self.pyodideUrl); + self.pyodide = await loadPyodide(); + self.pyodide.registerJsModule("flet_js", flet_js); + self.pyodide.globals.set("app_package_url", self.appPackageUrl); + self.pyodide.globals.set("python_module_name", self.pythonModuleName); + self.pyodide.globals.set("micropip_include_pre", self.micropipIncludePre); + flet_js.documentUrl = documentUrl; + await self.pyodide.loadPackage("micropip"); + await self.pyodide.runPythonAsync(` + import flet_js, micropip, os, runpy, sys, traceback + from pyodide.http import pyfetch + + py_args = flet_js.args.to_py() if flet_js.args else None + + if "app_package_url" in py_args: + app_package_url = py_args["app_package_url"] + + if app_package_url is None: + app_package_url = "assets/app/app.zip" + + if "python_module_name" in py_args: + python_module_name = py_args["python_module_name"] + + if python_module_name is None: + python_module_name = "main" + + if "micropip_include_pre" in py_args: + micropip_include_pre = py_args["micropip_include_pre"] + + if micropip_include_pre is None: + micropip_include_pre = False + + print("python_module_name:", python_module_name) + print("micropip_include_pre:", micropip_include_pre) + + if "script" not in py_args: + print("Downloading app archive") + response = await pyfetch(app_package_url) + await response.unpack_archive() + else: + print("Saving script to a file") + with open(f"{python_module_name}.py", "w") as f: + f.write(py_args["script"]); + + pkgs_path = "__pypackages__" + if os.path.exists(pkgs_path): + print(f"Adding {pkgs_path} to sys.path") + sys.path.insert(0, pkgs_path) + + if os.path.exists("requirements.txt"): + with open("requirements.txt", "r") as f: + deps = [line.rstrip() for line in f] + print("Loading requirements.txt:", deps) + await micropip.install(deps, pre=micropip_include_pre) + + if "dependencies" in py_args: + await micropip.install(py_args["dependencies"], pre=micropip_include_pre) + + # Execute app + runpy.run_module(python_module_name, run_name="__main__") + `); + await self.flet_js.start_connection(self.receiveCallback); + self.postMessage("initialized"); + } catch (error) { + self.postMessage(error.toString()); + } }; self.receiveCallback = (message) => { - self.postMessage(message); + self.postMessage(message.toJs()); } self.onmessage = async (event) => { // run only once if (!self.initialized) { self.initialized = true; + self.pyodideUrl = event.data.pyodideUrl; + self.flet_js.args = event.data.args; self.documentUrl = event.data.documentUrl; + self.appPackageUrl = event.data.appPackageUrl; self.micropipIncludePre = event.data.micropipIncludePre; self.pythonModuleName = event.data.pythonModuleName; await self.initPyodide(); @@ -44,4 +95,4 @@ self.onmessage = async (event) => { // message flet_js.send(event.data); } -}; \ No newline at end of file +}; diff --git a/client/web/python.js b/client/web/python.js index 21814aa8e..db2e34a9f 100644 --- a/client/web/python.js +++ b/client/web/python.js @@ -1,28 +1,70 @@ -const pythonWorker = new Worker("python-worker.js"); +const defaultPyodideUrl = "https://cdn.jsdelivr.net/pyodide/v0.27.7/full/pyodide.js"; -let _onPythonInitialized = null; -let pythonInitialized = new Promise((onSuccess) => _onPythonInitialized = onSuccess); -let _onReceivedCallback = null; +let _apps = {}; +let _documentUrl = document.URL; -pythonWorker.onmessage = (event) => { - if (event.data == "initialized") { - _onPythonInitialized(); - } else { - _onReceivedCallback(event.data); - } -}; +// This method is called from Dart on backend.connect() +// dartOnMessage is called on backend.onMessage +// it accepts "data" of type JSUint8Array +globalThis.jsConnect = async function(appId, args, dartOnMessage) { + let app = { + "dartOnMessage": dartOnMessage + }; + console.log(`Starting up Python worker: ${appId}, args: ${args}`); + _apps[appId] = app; + app.worker = new Worker("python-worker.js"); + + var error; + app.worker.onmessage = (event) => { + if (typeof event.data === "string") { + if (event.data != "initialized") { + error = event.data; + } + app.onPythonInitialized(); + } else { + app.dartOnMessage(event.data); + } + }; -documentUrl = document.URL; + let pythonInitialized = new Promise((resolveCallback) => app.onPythonInitialized = resolveCallback); -// initialize worker -pythonWorker.postMessage({ documentUrl, micropipIncludePre, pythonModuleName }); + // initialize worker + app.worker.postMessage({ + pyodideUrl: flet.noCdn ? flet.pyodideUrl : defaultPyodideUrl, + args: args, + documentUrl: _documentUrl, + appPackageUrl: flet.appPackageUrl, + micropipIncludePre: flet.micropipIncludePre, + pythonModuleName: flet.pythonModuleName + }); -async function jsConnect(receiveCallback) { - _onReceivedCallback = receiveCallback; await pythonInitialized; - console.log("Python engine initialized!"); + + if (error) { + console.log("Python worker init error:", error); + throw error; + } else { + console.log(`Python worker initialized: ${appId}`); + } } -async function jsSend(data) { - pythonWorker.postMessage(data); -} \ No newline at end of file +// Called from Dart on backend.send +// data is a message serialized to JSUint8Array +globalThis.jsSend = async function(appId, data) { + if (appId in _apps) { + const app = _apps[appId]; + app.worker.postMessage(data); + } +} + +// Called from Dart on channel.disconnect +globalThis.jsDisconnect = async function(appId) { + if (appId in _apps) { + console.log(`Terminating Python worker: ${appId}`); + const app = _apps[appId]; + delete _apps[appId]; + app.worker.terminate(); + app.worker.onmessage = null; + app.worker.onerror = null; + } +} diff --git a/client/windows/flutter/generated_plugin_registrant.cc b/client/windows/flutter/generated_plugin_registrant.cc index be1578b39..754c15c2f 100644 --- a/client/windows/flutter/generated_plugin_registrant.cc +++ b/client/windows/flutter/generated_plugin_registrant.cc @@ -13,9 +13,9 @@ #include #include #include -#include #include #include +#include #include #include @@ -34,12 +34,12 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("RecordWindowsPluginCApi")); RivePluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("RivePlugin")); - ScreenBrightnessWindowsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); + VolumeControllerPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("VolumeControllerPluginCApi")); WindowManagerPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("WindowManagerPlugin")); WindowToFrontPluginRegisterWithRegistrar( diff --git a/client/windows/flutter/generated_plugins.cmake b/client/windows/flutter/generated_plugins.cmake index 61e94283e..72a5864a6 100644 --- a/client/windows/flutter/generated_plugins.cmake +++ b/client/windows/flutter/generated_plugins.cmake @@ -10,15 +10,14 @@ list(APPEND FLUTTER_PLUGIN_LIST permission_handler_windows record_windows rive_common - screen_brightness_windows screen_retriever_windows url_launcher_windows + volume_controller window_manager window_to_front ) list(APPEND FLUTTER_FFI_PLUGIN_LIST - media_kit_native_event_loop ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/media/diagrams/chat-tutorial-layout.drawio b/media/diagrams/chat-tutorial-layout.drawio deleted file mode 100644 index f0040eb2c..000000000 --- a/media/diagrams/chat-tutorial-layout.drawio +++ /dev/null @@ -1 +0,0 @@ -7Vxbc5s4FP41ntl9iAcQAvwYO2l3Z5Jptu1ON/uyQ0C2NcXgBTl29tdX4mY4kgtxuWSTOpkMOkgCnct3LpIzQYvN4X3sbte3kU+CiaH5hwm6mhiG4yD+VxCeMoKNtYywiqmfkfQj4RP9j+TEotuO+iSpdWRRFDC6rRO9KAyJx2o0N46jfb3bMgrqT926KyIRPnluIFO/UJ+tc6quaccbvxG6WuePdor1bdyic05I1q4f7SskdD1BiziKWHa1OSxIIHhX8CUb9+7E3fLFYhKyNgM+vF9ffPnw7+Wdf//1Zrv9+/binz8u8lke3WCXLzh/WfZUcCCOdqFPxCTaBM33a8rIp63ribt7LnJOW7NNwFs6v8ynIzEjh5PvqZer51pDog1h8RPvUgwoGJZrDLLy9v7If1z0WVdYX3Z0c5mvyrmPbOEXOWeewSVD4tKdUBvIKb5mVmdHwuLoK1lEQRRzShiFvOd8SYMAkNyArkLe9DiPCKfPBQcp18LL/MaG+r54jJL/dQl1IQJUF4Exk0VgqSTQlwBQx2rqu8k67asDeUwMtMTiR9CjkFXo2UcSKr9jpZ9+WI9shfZrCt4buC/mm83MJ6F/KcBWqHDgJgn16vyuC4ccKPtLXE9x3rrP+4nrq0Ol29VT0Qj5UrJBuo0Lwn1K0DS9IBwHp63a6DsSU84RYV8VQyG+5ACArPhKo13skWaAYG68IqwJbmXZ12Qri7agxSRwGX2sv65K3PkT7iLKF1KqlgmA1YIaky0zH1X1JHAiG0ykgYkyPkgTpdpXLvt8hbQkhVxwU3VpyEVrLIRMD1s3FDN8jncnYfq52ByQJfseMiccbWi4ukm7XZlHysdckoIU8eHLIDWUNR9I+AzzreBTyjk857+clwthGfhKLAbP9WOb/4ruMYelkL++S1M9JW7C9iRhqZ0xl7kP6Uo78gUmbvYFujmkM7B7xCNj1h6ReKNHSLHGhAoELfxcqChdFXRnA0HFbMjAYbk0PE8VIPjWg4W7ChBMwFFHtkfTUQUIvRlkkatVuHxDE/ZIyT7D4+ufeNwZHlt2s/yHxWO9RRJ5NiBrdhWQp5pujwnKhaY3BnqzUdF7VlcRB4q+daAHsMZ2hkVvXU68e4TvF5X3WUg2a2Xe11vRQ2+RdD/TrIscrkzXsvytTOdOZG9dGrDZ0oAz1RvLgi0HGJ5+pgXb0IKNgS1YLh585OryhkOAGgR1EQ8AsMeKeEAVDvYXDuAWuBEEdJucqmdWAMRNtllVf0kPgmsQtH1MHN9UgbNjPKCuwBkDc7RMRcyt4LHZG4/lEkgv2NxYVzsGahaqBGoX2lRDBWGcSM1uC/T4RQE9LKW1Bnq7wWP0DfRyVeZ3LwrnO8ai8C3jfRcQD0pwtiI2HBbiHUnYn7lE31ES+G0y/mfF7BDxXeIslVUWy3PIw7KfKosS8dGgkD9oKavIhU7nPKeypC6YD/QdKUrOpmr/cdYX8w25xLFYu+yWJMl3NoLfArx1Hc7qddGbCqibDQl1hZV1X9wqdhcqOXCL3c965mwMmDqXx28aI6pRi18AuM1zM2eQVkmHS3qOp4zuqy9n6F3n6jOWVsxmU02bVT51JYGOo62SQE+FdTTF+rCK0uZ4Rlfp9tLxiHqL68HBYqe4Gx9gARvWjKmNJT9gGVMsK8yR2r0vUBw8oLEXkMtH7hPjtxwGdCB1UADHiqBv0CTHkJMcScKdBdzX6c9YATfkPZo5Eu/Lnfsq822jJ+YjOez6MxHHe7RfRKb5649JAljaKBmmgSHIyQiHVEVFoy94Q7InEQlOluG8Xr4rMns13/tzLKhFOfeV5vbm6Lk9anOe7P8dQOEXEkChUVzqKPv50KWa47tUuYD4yl0qHt2lFg8DLvV1e1T8Ajyq2duZtLHKJ3mI0Fh9y9zZWHUWuAsJD4y1LqyA7yrYMM3ruapiyvkHt57d5udW5g9uZYL9bnPs0yrmiznlVp6lGAdgCo1vBhhnTICxYGgLv1DQ+sAE2GnCA5+MM7v/Wt0rVzxjXMWzYUAPYpfWigdcGx7atbU5nzfgjua09m3OphNnY4RU2RbLWIoHo+uzQyqtL73jzeO/Mci6H/8XBLr+Bg== \ No newline at end of file diff --git a/media/diagrams/chat-tutorial-layout.svg b/media/diagrams/chat-tutorial-layout.svg deleted file mode 100644 index 65b629037..000000000 --- a/media/diagrams/chat-tutorial-layout.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
Page
Page
Container, expand=TrueListview, Expand=TrueRowIconButton
TextField, Expand=True
TextField, Expand=True
ChatMessageCircleAvatar
User (Text)
User (Text)
Messsage (Text)
Messsage (Text)
User (Text)
User (Text)
Message (Text)
Message (Text)
Column
Text is not SVG - cannot display
\ No newline at end of file diff --git a/media/diagrams/flet-highlevel-diagram-v2.drawio b/media/diagrams/flet-highlevel-diagram-v2.drawio deleted file mode 100644 index 024402b34..000000000 --- a/media/diagrams/flet-highlevel-diagram-v2.drawio +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/media/diagrams/flet-highlevel-diagram.drawio b/media/diagrams/flet-highlevel-diagram.drawio deleted file mode 100644 index 74f627769..000000000 --- a/media/diagrams/flet-highlevel-diagram.drawio +++ /dev/null @@ -1 +0,0 @@ -7Vttd6I4GP01nrP7QQ8ERPxY32Y60+50x50zux8jRM0WCQux1f76TSAoedHaivZt2tMjPIQAz819cnOlDae/WH1KYTK/JiGKGsAKVw1n0ADAtv02++CRdRHxOt0iMEtxKBptA2P8gETQEtElDlEmNaSERBQncjAgcYwCKsVgmpJ7udmURPJVEzhDWmAcwEiP/sQhnZfPZVnbA58Rns3Fpf22OLCAZWMRyOYwJPeVkDNsOP2UEFpsLVZ9FPHklXkpzhvtOLq5sRTF9JATPl+R669o9ueX4d+Ujn58H1qdy6Yj0LiD0VI8sbhbui5TkJJlHCLei91wevdzTNE4gQE/es9AZ7E5XUTi8JTEVKBo830Y4VnMdiI0ZXfZy2hKblGfRCTNO3c8Z+CNRpsjZY5ZdnrizlBK0WrnM9ubTLIhiMgC0XTNmogTOh2R/PUGt2L/voKlJ2LzCoygDEIxfmabvrcpZhsiy0/IuGs/nnHWDRvf6PFswywpBv0UrzhCPRmrp2S7Cpwr9itntv3ecOjogNYAku+0JZAcRwfJMWDknAwi8BKkqIIB00C0900wjvz+sN+vJ/u2o1AEdLXsu76JIuBk+Xe1/P9EkzEJbhHNGoB1al3e9PdAYj0OyV4CdCsYBSyPKNVQiEmMdJYUPzyOo0hpWgNWwJexAgasbCNWNUA1inr/dp1gMfhG/X/Wg6/NbxcPTVuHSq9mcXjBZ2KezAhmGQ5kKHZkAVgolOZmPS2Vx24bnrqMpSiCFN/JM7opFeIKNwSzO9lk3bOUrFt+y/LlXjKyTAMkTqxOwkpfHRVBQ18UpjNEtb5yfDYP/3x2eQbEPLjg/IgnWZIDYn0Zf/uDfSyTEFImv7QG6r4KOUPuCk6YFpSnK5VTnA2Yia0LcWCBw5D30UtRhh/gJO+Psznh2cjz0+412gNDTY345XowuJ3ldaBCymn+s4+BQjyK620lW3VM7hn+O/natFpMgvgS4m4tYxIoU2arC+QuyHSaoZOMH/94xh+MlVIBDqqV5ykKGpE7Sok9tCJo1UXt6MTlwLQA0MrB8A5x+r2BMnAUx3eP9xegeFPheNM9F8MNk/rNms5JXGgv9ucUW59I8Ylo0NLAP0ohV/VTVfAaRdhWLYOa1o22q5CyaxBarqG6uKfSxHbn+LL72muq6ylZ97rPq6maTlY7OnFNtU1z5K+iqg3nD1VVy9y8ZwZrxDtiqaRVg/MvlYDJq9N4fMMt5De1VqqfzxVH+8wLIVfq9JwLIaDLpEuOVSwuZ3Z0szlMUM5vsgwfF0ayiqoCCR43qZhy6uW/Ow2vSkt3wH/r0U+eY7fkOmsSUKWmqRYiv7N7BBwloEqTuYLWNZngiFM3RHeY5V8FjT0/NbmGqgeoe32Hk9cEv2xkmoz5sbhJS4dxY9TXAKOvrE19TwfROpHdaHaGHQ3EMUrZM74P7Abd7qjr1YOdY8vY2Vb7pcHTGVi19XcuH9+rm685P+d0882qXAOoTxYJq5Ehi46iJeW5Y9dNEqZqIs6vSSqh5v235F9n54lsZnn6L1gD205WhRQSx9nWjH+20AoVFkILJrdiCydwz2g43kzoesNh5UvQ3WZCTVVUswrL/ZczE3QmVoDmrk6B8WtwdKx6QNBWEy/v6Ohz2Y8s51eSklkKF/Xlv1167jckwxQT4/R2pTTYTHOPToiUJBJmHWWmU+EXIGtatU4Fqjp4tnMg3sA6FeCOycITZbQsiPx1olaxkvzt9/Iou9qmAR8RKA6zhnh1qVx35ou3xQIWhyjJCzZfiLDmb0Qg7V2ZPgX5jrL2sDtWyyB+SidI8kG6pwJftwQHOEsgDebcM7CQMAML6Ng6nIER6aroA6EG2uBQ1MAG3/pFqwbbflr9qtHPn5NfQY3WhdFfKYyzqHD29pVcHOfMFS9OsGClUlfLcRBhFOsG0bsltutp5dgHL1yMgT4TfydLKhfiCQxuS+wgB32/OPtAAL6Oygz2U1UQUdByu3hldzwz+Q3vFr9Oaalu6Gdw8czYnUoLaciNRaGUVZAy01p3mPNwj2f0YTAEjn0+DM1vvu3SRTvmtw+miwxvs2oD4GDDyAYHGkbP0UVsd/s/KMVXY9v/5HGG/wM= \ No newline at end of file diff --git a/media/diagrams/flet-mobile-update.drawio b/media/diagrams/flet-mobile-update.drawio deleted file mode 100644 index b0d57a160..000000000 --- a/media/diagrams/flet-mobile-update.drawio +++ /dev/null @@ -1 +0,0 @@ -7Zpdc9o4FIZ/DTO7FzC2jG245LPdtmnS0t22VzuKLcAbGbmyINBfv5ItgWUZ8KSQkKRJp7GOPmyfR+eVZKnhDOL1GwqT+RUJEW4AK1w3nGEDgG7b4/8LwyY3eFY3N8xoFOYme2eYRD+RNFrSuoxClGoFGSGYRYluDMhigQKm2SCl5F4vNiVYv2sCZ8gwTAKITevXKGRzabUta5fxFkWzubx1x5UZMVSFpSGdw5DcF0zOqOEMKCEsv4rXA4SF75Rf8nrjPbnbB6NowepU+PoP6fS+vHF706kLP24GcLK6a4K8lRXES/nC8mHZRnmAkuUiRKIRu+H07+cRQ5MEBiL3niPntjmLscyekgWTEG3+hn1IA5l0eSpllNyhAcGEcsuCLHhGXz4Bogyt976avXUY72iIxIjRDS+iKnSljzeq88j0/Q6Z15G2eYEWULSg7Cazbds7T/IL6cxqx779QK7eo9mnd6NvjI3//jyy/L+adtvw7M2GzcmC2+hywaIYnc7TWTrCWHm2AZxxZzAaDPb5fIdFuLW/x98VVPYiAJaOAHS7BgK7XYGgfTYCBoABiZMIcwcDa4yXjCEq7pskDeBh/jT9W6rx8H4sRWxmnm6mmbt6vIBtJ+vMZyqfX83E3xZac6b8Ya0WTO7kVZTAs3LueqPReFyDs3Mazj54Qs5j3P+v6wTx8Jp1vm+G75vXvZ8VnMcYMTEk4Ei858m8LxQMw1uEb0gasYiHsjMM+B14N3L6woMRHzY+lArEURjiDAWOZpU1ejKDkUQj5qvby0cvw5eIde68R7SH4vc8tG2nJu2tHJw+rJ39wppQMqMw/o38hEJ+Acjb5lj6Fd1OSHCHWHoAtnUcdu5KNbMDOv5uFcNKnRWVCjzyn2p4J0DkuCUNBhWIrEpE59Jgk5DBBS3CnpiQC2dimKZRoKPg/qCbbxJblvguEi3gqvRwXcwdblRqHbFvqhF+XazGk7taIqEq7cWQkiUN0KHemJdjkM4QOz4uoVBbYJhQC9DcCmbKRhGGLFrpy5IqkPIONyQSg5/qM155fmZ1WlZHbyV/dVmxuJIoteV3jreVu8doK+td25d/uCR4Ff3Ng7GI7sVtmmSArXeT64/8zzIJIeNrSKNAOV3usJxcpu16Pz2q6lv9p4jPGuFt1p7oc4nwRuYft99whxWTu2ys6cPgbpapWEFSptlPZcc9HJHmukouleWDNYqr0Sq1afJY8v2ORrx9kj5p+1qjTVdvgEynKTpL7+n8ulrVIKV0ydJ0yT+sS1sVtHUV9I+pYC1BOyRURUE7JHwXImiGCPmlwa2umhnKWG7ozFLWrSNloxUS0vEMJMzshwdD8ILlqalkQX26fCx5Urr4S7OpyxOfurOpg8u+C1GftlcSDa/7MPUBZRkrN3Rm9bGrxsKXLz/bELto/XGfRn/UMvE1rOaUrjyz2Y8hG7+wnDO07PGXc8D8iFuhQjdir+5ZredqqtE24C5YjZ5ssdZ2jb7xqr/+efbxr3/dM338q5ZQE1Bhp+0NachNtr2kHnG38+BgUZ+BIZlPvtvpVK0Z5Lam2qAUZxVauXr+8afK5XfbFhCA+MCeNuS5CKW1mQ7FMcyzmCAq99hSRFdiG7XElruRVYVaOXDMAKmvxlU9Ro/+42r8gN0Rv6XPymzfUpYifDV308b+7rngm5P4YZQmkAVzMU5aSE7fc3R8SOEwsKmdr4gacEFdamDL9/QbWwY2EVbh/rj6vZH5cI2+gI1Mc6D8QuEixfl09pDm8qmYCF25o8GNBaku6vGeMw8vNrLbnqHHHfDEagzMofgzWTJdiW9hcKfYQQE9zU4j7TvB8IoAXoY0g8OhKgNRhuXuNBl/4lnVsuTF8vOBvi4EVm1255oMGeQmUij1aVBpBmutIhGHB5aWr4eh+kj1GAx/0N7nVfB9jtvhvyEnaF/d44ozfXPGkuz85Zj/s4Hfsvgvf/9eQujrHu/s2rAeIJY8uTuhnn/R2R3zd0b/Aw==7Vldj+MmFP01kdqHRDaOneQxySRTVbvbadNqd/tGbBKjxcbC5Gt+fcEGf0FmopnxaLXdZOSBC1zsew6HazLwlsn5nsEs/kgjRAbAic4D724AwGwciKs0XEpD4MxKw57hqDS5tWGDH5EyOsp6wBHKWx05pYTjrG0MaZqikLdskDF6anfbUdKeNYN7ZBg2ISSm9TOOeFxap75T239DeB/rmV1HtSRQd1aGPIYRPTVM3mrgLRmlvCwl5yUiMnY6LuW49ZXW6sYYSvktA06Abv+6n39chP+uXDR9vEP861B5OUJyUA+sbpZfdAQYPaQRkk7cgbc4xZijTQZD2XoSkAtbzBOimnc05QpEVzzhArJQVX1Ryzmj39CSEsqEJaWpaFioO0CMo/PVR3OrgAmiIZogzi6iix7gqRhfOvVTDZmvIYsbcFXgQEWTfeW7jqQoqGDaA3tMPuEPdD/cnkHM/ozP9xAchp4R2DVBXBKVYPlUbxZlGVcCt4g80BxzTFNhDMUMSIR4IaOKBZk/dDokOIrkzAtI8N46Yq4aOM1aME709OrWi9vBhGhQB8Db+fJrwC1aguKjPDTs5afiggG8hR5XueAbXJgZXHDHNi44fXFhbHDh4cJjAcRPEvREggrgJ0hQ4f0+JDCVVpPAYYeU4wSJ0i9ZYRuhM/r17dhhgWc9Xa6Wy2uSXCPt+f0AAmY3AjLuCw9gEegDl/wXk2WZBKN/GGbBarVePw8D6EkcbTBYxbE3GHSe91QGgtJoLlM5KVEE5jkO20FGZ8y/iLKjyl9leTTxVfXu3Gi7u+hKKm7/i/YgK81hsl6PK2p64FUYcnpgIXo+J+CQ7RF/fr9AUSs1NUFtZjcWzLSNIQI5PrYTWhuQaoYHimWCojkzDjqcmc7aLsrnVqOaCWjHEZh2HAUdR2VgDEcFr6rHfgXVbMluABO5ZNNtnhXYOqujwDU3G7r1LkcFWMUG26bms1trtQkzlONHuC38SZplMgxFYPzFwL+zaIlJxKdXmJlhq5cmNeug+V5iU4+hMwKTybQF4vhNODYEba96qWgPdLfLUT+kMPeBF+uP29IfcKP+OG39Af3pz/hG/fG+K/0xZMOZjpzpyySoq2VuV8v6liDztdAiQQ/yTAI4hyyCHP1QUqQX23csRdqNcuq/lxABc3f6m8E0JyUHnN83f3ySPlJOxb86VxWLUDA2N1ggAszbBLDmmM2EVKedNxPFlvzW6bFzE1FekMNOOnqgMGrmsBq39rvddS687lXC3EM2Ys+QqCGVTTgKteIIKEfsWGB3xFBcP6Pthobf/t8g+uaJ3TuDaEozD0Us5tItWLtgMnLEVzz/PKNMwBgQCc1W4BjsZemQ4nPVXfwdoWhai3d7cd0RZB75/bDgepPOjg1uXqEjvy94fQPef/JiEWaM7hlMDHhede7d42Fc3fPJ87g3wNH1O9nSxLZILZmf298qDQwYpahGP9Ez0OvmzTb0bGn7S8CTQlf9kFVmRPWvgd7qPw==7Zjvj5owGMf/GpLthYZSQe/lqXC325ZpLpdlL6sU6K5QVuv5469fCwVBcHoJLFkyNQrflrZ8P88DDxpwFu8fOEqjr8zH1LBMf2/AuWFZDgTyWwmHXBiZTi6EnPi5BE7CMzliLZpa3RIfb2odBWNUkLQurlmS4LWoaYhztqt3Cxitz5qiEDeE5zWiTfU78UWUqxPbPOmPmIRRMTMwdUuMis5a2ETIZ7uKBF0DzjhjIt+K9zNMlXeFL/lx3oXWcmEcJ+KWA3w8t1cRenlcOp/dqTtYuotvAz3KG6JbfcJ6seJQOMDZNvGxGgQYcLqLiMDPKVqr1p1ELrVIxFQ3BywRGiJQ+4iSMJE7FAdykdON4OwVzxhlPBscOnDueF7ZUlgszZnqlWEu8P7iKYPSSBmAmMVY8IPsog8Yj7X3h3pQ7U4kLUdrUYUiLESkoycshz4ZLDe0x+/we9zw26NYqPilRJ1UZ+bbcp+iFaYLtiGCMAVhLWfA0vipMpXIGP9y1iEmvq9mLrE1jrjXDYKpuRFfF/ONivn12rP1EEoL1glLcFsAjObq3Q3uiVnHPWrBbbbgnnRAe5eu0qdlvEz940IED+DpJ0wHVoP24iAiafW/inl8hbIEGtjq3Zrr2UuPUNHzVxkCDd4tUXFzCADnxhgAXaR8axDAi0Fg8m0iSIzl1oc004Z4jz92ewU+w+NNZu5s1sCj0/NEGtr9ACmT7RqQUV88QDMrXzYy+uWJchZyFHfnv9Vvdp56/jFBu6AIztLqrkkRWG2XVtAXxWZaqTup/x9eA97d6Do8uyd2rUVQMwEb1OQossLH14mhTZqX/QHZK8rTOu/3FJxV+EU5UznSnkxdFzZr2i4Kl4ldIwRhkxBsq1P7KlNHLcm1FSK7SqI0NSyHqlNf8Ro159dWPcxkxg02mZP3sgMA6T7zqWiXW6H6HaL01bDkCs0hSVGvt707x3Ur2C/f9ib9lKJ/8653XB1fnr6Ow4DjeOD8iNf3zGt50PO8T1WOOZJtQvYZNS//vCHZ5Mk6RX4H2ZPKGSRpkaiTaHW45VHg9itkG/pTcJgt9LsgCMfDelaClqwElt2EKOPZfjdGZXP5X0DWVvlDBbq/AQ== \ No newline at end of file diff --git a/media/diagrams/page-views.drawio b/media/diagrams/page-views.drawio deleted file mode 100644 index 96cf231ed..000000000 --- a/media/diagrams/page-views.drawio +++ /dev/null @@ -1 +0,0 @@ -7Zhdb5swFIZ/DZeNwIZALpukaaVparVO23rpggPeDEbGaUh//exgEz7SJUuTJtJWVa05NsfmOe+xD1hwkpa3HOXJZxZhagE7Ki04tQBwXAAs9WtHq8oSOF5liDmJ9KCN4ZG8Ym20tXVBIly0BgrGqCB52xiyLMOhaNkQ52zZHjZntD1rjmLcMzyGiPat30kkEm11bHvTcYdJnOipA093pMgM1oYiQRFbNkzwxoITzpioWmk5wVTBM1yq+2Zv9NYL4zgT+9wwo+OfIxim03sRPK2mn67ur1+vtJcXRBf6gR8UkGrBYmUocLbIIqwc2RYcLxMi8GOOQtW7lHGXtkSkVF45sjlnmdCBlMGG4/5CzayYC1w2THrht5ilWPCVHGJ6hxqiVhEw+JeNmBjQSSMc9UCkdRDXvjeoZEPT+gty4ATkjkDKDdqkHP/spOBlkvLsDqmRf25Sbo/UN4KXF5eNHuhkI3DPTc4cFw1078RGKJ0wyvj6Xjifz0EYnhufeyJ63m54DTT1QaYwRqhIaqaIkjiT7VDCwZLcWEEh8jC91h0piSLlUfnIlee0jFXdMKiOalD9V24VG3vgu6qtuPkDF6iwUJLf6XVkTISJnvkoYQnaYYFOPyxuPyrOyTTdP2IuV9MHb6an0rT/X9MqLB1N21u2mg/VdL8YuFhNH15KnUrTTr9A6NHDWXRdCW4aUlQUJNyzcsJR602oj6Xx2N6WpzY2jikS5KX9/rQNhZ7hgRG5kpo6GLWpw27NULAFD7G+q/m+03Hk+jscCcRjLHqO1pGpH/sdweqfql9Zrgzrms4mhXotJkVO0UqqvRtHKVLRjlwhOPuFjdgzluGO/rVp/x1rW261s69ZPR5pTxp20gr6W44Ke2v1eKq02uOsaKSVxtyKDOLCdDPpxthmhJox0oEGCU1v4/pNrpXa/7B4vSVUYt5DjheS5l0VuIemuQ87cvI+OM2Dnni+qG86Js+HVKXxM5etWLVClGXr7mesIs7yfyr3h3scqc6H5v7o7LmPSyJ+mJGy/bQuEz19NS11ONYXq1214c79wlRguzeM4LI2DK+b56MBsBs/3oH7hzMcOCPb9Yf6b3uWwD/SbiIvN996q+GbL+bw5jc= \ No newline at end of file diff --git a/media/icons/macos/flet-png/app_icon_1024.png b/media/icons/macos/flet-png/app_icon_1024.png deleted file mode 100644 index cd0e22267..000000000 Binary files a/media/icons/macos/flet-png/app_icon_1024.png and /dev/null differ diff --git a/media/icons/macos/flet-png/app_icon_128.png b/media/icons/macos/flet-png/app_icon_128.png deleted file mode 100644 index a7f8559d3..000000000 Binary files a/media/icons/macos/flet-png/app_icon_128.png and /dev/null differ diff --git a/media/icons/macos/flet-png/app_icon_16.png b/media/icons/macos/flet-png/app_icon_16.png deleted file mode 100644 index e6d526071..000000000 Binary files a/media/icons/macos/flet-png/app_icon_16.png and /dev/null differ diff --git a/media/icons/macos/flet-png/app_icon_256.png b/media/icons/macos/flet-png/app_icon_256.png deleted file mode 100644 index 117ff2414..000000000 Binary files a/media/icons/macos/flet-png/app_icon_256.png and /dev/null differ diff --git a/media/icons/macos/flet-png/app_icon_32.png b/media/icons/macos/flet-png/app_icon_32.png deleted file mode 100644 index 541cd5d60..000000000 Binary files a/media/icons/macos/flet-png/app_icon_32.png and /dev/null differ diff --git a/media/icons/macos/flet-png/app_icon_512.png b/media/icons/macos/flet-png/app_icon_512.png deleted file mode 100644 index f89a749ad..000000000 Binary files a/media/icons/macos/flet-png/app_icon_512.png and /dev/null differ diff --git a/media/icons/macos/flet-png/app_icon_64.png b/media/icons/macos/flet-png/app_icon_64.png deleted file mode 100644 index 2a05709be..000000000 Binary files a/media/icons/macos/flet-png/app_icon_64.png and /dev/null differ diff --git a/media/icons/macos/flet-psd/flet-macos-icon-white-bg-1024.psd b/media/icons/macos/flet-psd/flet-macos-icon-white-bg-1024.psd deleted file mode 100644 index d8e6d75dd..000000000 Binary files a/media/icons/macos/flet-psd/flet-macos-icon-white-bg-1024.psd and /dev/null differ diff --git a/media/icons/macos/flet-psd/flet-macos-icon-white-bg-128.psd b/media/icons/macos/flet-psd/flet-macos-icon-white-bg-128.psd deleted file mode 100644 index fb70ceabe..000000000 Binary files a/media/icons/macos/flet-psd/flet-macos-icon-white-bg-128.psd and /dev/null differ diff --git a/media/icons/macos/flet-psd/flet-macos-icon-white-bg-16.psd b/media/icons/macos/flet-psd/flet-macos-icon-white-bg-16.psd deleted file mode 100644 index c00c027eb..000000000 Binary files a/media/icons/macos/flet-psd/flet-macos-icon-white-bg-16.psd and /dev/null differ diff --git a/media/icons/macos/flet-psd/flet-macos-icon-white-bg-256.psd b/media/icons/macos/flet-psd/flet-macos-icon-white-bg-256.psd deleted file mode 100644 index f1c2e357f..000000000 Binary files a/media/icons/macos/flet-psd/flet-macos-icon-white-bg-256.psd and /dev/null differ diff --git a/media/icons/macos/flet-psd/flet-macos-icon-white-bg-32.psd b/media/icons/macos/flet-psd/flet-macos-icon-white-bg-32.psd deleted file mode 100644 index 81cd5f15b..000000000 Binary files a/media/icons/macos/flet-psd/flet-macos-icon-white-bg-32.psd and /dev/null differ diff --git a/media/icons/macos/flet-psd/flet-macos-icon-white-bg-512.psd b/media/icons/macos/flet-psd/flet-macos-icon-white-bg-512.psd deleted file mode 100644 index b19f52f4c..000000000 Binary files a/media/icons/macos/flet-psd/flet-macos-icon-white-bg-512.psd and /dev/null differ diff --git a/media/icons/macos/flet-psd/flet-macos-icon-white-bg-64.psd b/media/icons/macos/flet-psd/flet-macos-icon-white-bg-64.psd deleted file mode 100644 index a437d3dcd..000000000 Binary files a/media/icons/macos/flet-psd/flet-macos-icon-white-bg-64.psd and /dev/null differ diff --git a/media/icons/macos/templates/apple-original.7z b/media/icons/macos/templates/apple-original.7z deleted file mode 100644 index 7466021b0..000000000 Binary files a/media/icons/macos/templates/apple-original.7z and /dev/null differ diff --git a/media/icons/macos/templates/macos-icon-white-bg-1024.psd b/media/icons/macos/templates/macos-icon-white-bg-1024.psd deleted file mode 100644 index 6f6847fe5..000000000 Binary files a/media/icons/macos/templates/macos-icon-white-bg-1024.psd and /dev/null differ diff --git a/media/icons/macos/templates/macos-icon-white-bg-128.psd b/media/icons/macos/templates/macos-icon-white-bg-128.psd deleted file mode 100644 index a4ea49a19..000000000 Binary files a/media/icons/macos/templates/macos-icon-white-bg-128.psd and /dev/null differ diff --git a/media/icons/macos/templates/macos-icon-white-bg-16.psd b/media/icons/macos/templates/macos-icon-white-bg-16.psd deleted file mode 100644 index 53e6560a3..000000000 Binary files a/media/icons/macos/templates/macos-icon-white-bg-16.psd and /dev/null differ diff --git a/media/icons/macos/templates/macos-icon-white-bg-256.psd b/media/icons/macos/templates/macos-icon-white-bg-256.psd deleted file mode 100644 index b7bceb645..000000000 Binary files a/media/icons/macos/templates/macos-icon-white-bg-256.psd and /dev/null differ diff --git a/media/icons/macos/templates/macos-icon-white-bg-32.psd b/media/icons/macos/templates/macos-icon-white-bg-32.psd deleted file mode 100644 index 548a98519..000000000 Binary files a/media/icons/macos/templates/macos-icon-white-bg-32.psd and /dev/null differ diff --git a/media/icons/macos/templates/macos-icon-white-bg-512.psd b/media/icons/macos/templates/macos-icon-white-bg-512.psd deleted file mode 100644 index 432e726d4..000000000 Binary files a/media/icons/macos/templates/macos-icon-white-bg-512.psd and /dev/null differ diff --git a/media/icons/macos/templates/macos-icon-white-bg-64.psd b/media/icons/macos/templates/macos-icon-white-bg-64.psd deleted file mode 100644 index 6070bc011..000000000 Binary files a/media/icons/macos/templates/macos-icon-white-bg-64.psd and /dev/null differ diff --git a/media/logo/Icon-192.png b/media/logo/Icon-192.png deleted file mode 100644 index 07f9ac603..000000000 Binary files a/media/logo/Icon-192.png and /dev/null differ diff --git a/media/logo/Icon-512.png b/media/logo/Icon-512.png deleted file mode 100644 index aa29fc543..000000000 Binary files a/media/logo/Icon-512.png and /dev/null differ diff --git a/media/logo/app_icon_1024.png b/media/logo/app_icon_1024.png deleted file mode 100644 index c84ad9ad5..000000000 Binary files a/media/logo/app_icon_1024.png and /dev/null differ diff --git a/media/logo/app_icon_128.png b/media/logo/app_icon_128.png deleted file mode 100644 index 330de989e..000000000 Binary files a/media/logo/app_icon_128.png and /dev/null differ diff --git a/media/logo/app_icon_16.png b/media/logo/app_icon_16.png deleted file mode 100644 index fd20b95c6..000000000 Binary files a/media/logo/app_icon_16.png and /dev/null differ diff --git a/media/logo/app_icon_256.png b/media/logo/app_icon_256.png deleted file mode 100644 index 2d62b439f..000000000 Binary files a/media/logo/app_icon_256.png and /dev/null differ diff --git a/media/logo/app_icon_32.png b/media/logo/app_icon_32.png deleted file mode 100644 index c360b8ca9..000000000 Binary files a/media/logo/app_icon_32.png and /dev/null differ diff --git a/media/logo/app_icon_512.png b/media/logo/app_icon_512.png deleted file mode 100644 index b1bc19abf..000000000 Binary files a/media/logo/app_icon_512.png and /dev/null differ diff --git a/media/logo/app_icon_64.png b/media/logo/app_icon_64.png deleted file mode 100644 index 7b4af64d8..000000000 Binary files a/media/logo/app_icon_64.png and /dev/null differ diff --git a/media/logo/favicon.png b/media/logo/favicon.png deleted file mode 100644 index c360b8ca9..000000000 Binary files a/media/logo/favicon.png and /dev/null differ diff --git a/media/logo/flet-logo-256x256.png b/media/logo/flet-logo-256x256.png deleted file mode 100644 index 99efba06a..000000000 Binary files a/media/logo/flet-logo-256x256.png and /dev/null differ diff --git a/media/logo/flet-logo-512x512.png b/media/logo/flet-logo-512x512.png deleted file mode 100644 index 97272a4e6..000000000 Binary files a/media/logo/flet-logo-512x512.png and /dev/null differ diff --git a/media/logo/flet-logo-no-text.svg b/media/logo/flet-logo-no-text.svg deleted file mode 100644 index e9c0bb905..000000000 --- a/media/logo/flet-logo-no-text.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/media/logo/flet-logo-v2-512-white.png b/media/logo/flet-logo-v2-512-white.png deleted file mode 100644 index 1318392b6..000000000 Binary files a/media/logo/flet-logo-v2-512-white.png and /dev/null differ diff --git a/media/logo/flet-logo-v2-512.png b/media/logo/flet-logo-v2-512.png deleted file mode 100644 index 3ac35c020..000000000 Binary files a/media/logo/flet-logo-v2-512.png and /dev/null differ diff --git a/media/logo/flet-logo-white-text.svg b/media/logo/flet-logo-white-text.svg deleted file mode 100644 index be12f4a02..000000000 --- a/media/logo/flet-logo-white-text.svg +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/media/logo/flet-logo.cdr b/media/logo/flet-logo.cdr deleted file mode 100644 index cc3112ce6..000000000 Binary files a/media/logo/flet-logo.cdr and /dev/null differ diff --git a/media/logo/flet-logo.png b/media/logo/flet-logo.png deleted file mode 100644 index c2846a07c..000000000 Binary files a/media/logo/flet-logo.png and /dev/null differ diff --git a/media/logo/logo-inkscape.svg b/media/logo/logo-inkscape.svg deleted file mode 100644 index 156b8ec1f..000000000 --- a/media/logo/logo-inkscape.svg +++ /dev/null @@ -1,549 +0,0 @@ - - - -white rounded squaremaskableFletFletfletFletFletfletApple touchTransparentFavicon transparentMaskableRound for WindowsDevelop Android apps in Python only diff --git a/media/pictures/plus-icon.svg b/media/pictures/plus-icon.svg deleted file mode 100644 index 55922d574..000000000 --- a/media/pictures/plus-icon.svg +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - + - - diff --git a/media/website/homepage-icons.cdr b/media/website/homepage-icons.cdr deleted file mode 100644 index 50023ea59..000000000 Binary files a/media/website/homepage-icons.cdr and /dev/null differ diff --git a/packages/flet/lib/flet.dart b/packages/flet/lib/flet.dart index ec32a4a3f..371541f67 100644 --- a/packages/flet/lib/flet.dart +++ b/packages/flet/lib/flet.dart @@ -1,20 +1,14 @@ library flet; -export 'src/control_factory.dart'; -export 'src/controls/create_control.dart'; -export 'src/controls/error.dart'; -export 'src/controls/flet_store_mixin.dart'; +export 'src/controls/base_controls.dart'; +export 'src/extensions/control.dart'; export 'src/flet_app.dart'; export 'src/flet_app_errors_handler.dart'; -export 'src/flet_control_backend.dart'; -export 'src/models/app_state.dart'; -export 'src/models/asset_src.dart'; +export 'src/flet_backend.dart'; +export 'src/flet_extension.dart'; +export 'src/flet_service.dart'; +export 'src/models/asset_source.dart'; export 'src/models/control.dart'; -export 'src/models/control_ancestor_view_model.dart'; -export 'src/models/control_tree_view_model.dart'; -export 'src/models/control_view_model.dart'; -export 'src/models/controls_view_model.dart'; -export 'src/models/page_args_model.dart'; export 'src/models/page_size_view_model.dart'; export 'src/utils.dart'; export 'src/utils/alignment.dart'; @@ -24,22 +18,19 @@ export 'src/utils/autofill.dart'; export 'src/utils/badge.dart'; export 'src/utils/borders.dart'; export 'src/utils/box.dart'; -export 'src/utils/browser_context_menu.dart'; export 'src/utils/buttons.dart'; -export 'src/utils/charts.dart'; -export 'src/utils/client_storage.dart'; -export 'src/utils/clipboard.dart'; export 'src/utils/collections.dart'; export 'src/utils/colors.dart'; -export 'src/utils/cupertino_colors.dart'; -export 'src/utils/cupertino_icons.dart'; export 'src/utils/dash_path.dart'; export 'src/utils/debouncer.dart'; export 'src/utils/desktop.dart'; export 'src/utils/dismissible.dart'; export 'src/utils/drawing.dart'; export 'src/utils/edge_insets.dart'; +export 'src/utils/events.dart'; export 'src/utils/form_field.dart'; +export 'src/utils/geometry.dart'; +export 'src/utils/gesture_detector.dart'; export 'src/utils/gradient.dart'; export 'src/utils/icons.dart'; export 'src/utils/images.dart'; @@ -49,14 +40,14 @@ export 'src/utils/markdown.dart'; export 'src/utils/material_icons.dart'; export 'src/utils/material_state.dart'; export 'src/utils/menu.dart'; +export 'src/utils/misc.dart'; export 'src/utils/mouse.dart'; export 'src/utils/networking.dart'; export 'src/utils/numbers.dart'; -export 'src/utils/others.dart'; export 'src/utils/overlay_style.dart'; export 'src/utils/platform.dart'; -export 'src/utils/platform_utils_non_web.dart' - if (dart.library.js) "src/utils/platform_utils_web.dart"; +export 'src/utils/platform_utils_web.dart' + if (dart.library.io) "src/utils/platform_utils_non_web.dart"; export 'src/utils/responsive.dart'; export 'src/utils/strings.dart'; export 'src/utils/text.dart'; @@ -65,3 +56,6 @@ export 'src/utils/theme.dart'; export 'src/utils/time.dart'; export 'src/utils/tooltip.dart'; export 'src/utils/transforms.dart'; +export 'src/utils/uri.dart'; +export 'src/widgets/error.dart'; +export 'src/widgets/flet_store_mixin.dart'; diff --git a/packages/flet/lib/src/actions.dart b/packages/flet/lib/src/actions.dart deleted file mode 100644 index 52bbc4339..000000000 --- a/packages/flet/lib/src/actions.dart +++ /dev/null @@ -1,126 +0,0 @@ -import 'dart:ui'; - -import 'flet_control_backend.dart'; -import 'flet_server.dart'; -import 'models/window_media_data.dart'; -import 'protocol/add_page_controls_payload.dart'; -import 'protocol/app_become_active_payload.dart'; -import 'protocol/app_become_inactive_payload.dart'; -import 'protocol/append_control_props_request.dart'; -import 'protocol/clean_control_payload.dart'; -import 'protocol/invoke_method_payload.dart'; -import 'protocol/page_controls_batch_payload.dart'; -import 'protocol/page_media_data.dart'; -import 'protocol/register_webclient_response.dart'; -import 'protocol/remove_control_payload.dart'; -import 'protocol/replace_page_controls_payload.dart'; -import 'protocol/session_crashed_payload.dart'; -import 'protocol/update_control_props_payload.dart'; - -class PageLoadAction { - final Uri pageUri; - final String assetsDir; - final FletControlBackend backend; - PageLoadAction(this.pageUri, this.assetsDir, this.backend); -} - -class PageReconnectingAction { - final String connectMessage; - final int nextReconnectDelayMs; - PageReconnectingAction(this.connectMessage, this.nextReconnectDelayMs); -} - -class PageSizeChangeAction { - final Size newPageSize; - final WindowMediaData? wmd; - final FletControlBackend backend; - PageSizeChangeAction(this.newPageSize, this.wmd, this.backend); -} - -class SetPageRouteAction { - final String route; - final FletServer server; - SetPageRouteAction(this.route, this.server); -} - -class WindowEventAction { - final String eventName; - final WindowMediaData wmd; - final FletControlBackend backend; - WindowEventAction(this.eventName, this.wmd, this.backend); -} - -class PageBrightnessChangeAction { - final Brightness brightness; - final FletControlBackend backend; - PageBrightnessChangeAction(this.brightness, this.backend); -} - -class PageMediaChangeAction { - final PageMediaData media; - final FletControlBackend backend; - PageMediaChangeAction(this.media, this.backend); -} - -class RegisterWebClientAction { - final RegisterWebClientResponse payload; - final FletControlBackend backend; - RegisterWebClientAction(this.payload, this.backend); -} - -class AppBecomeActiveAction { - final FletServer server; - final AppBecomeActivePayload payload; - AppBecomeActiveAction(this.server, this.payload); -} - -class AppBecomeInactiveAction { - final AppBecomeInactivePayload payload; - AppBecomeInactiveAction(this.payload); -} - -class SessionCrashedAction { - final SessionCrashedPayload payload; - SessionCrashedAction(this.payload); -} - -class InvokeMethodAction { - final InvokeMethodPayload payload; - final FletServer server; - InvokeMethodAction(this.payload, this.server); -} - -class AddPageControlsAction { - final AddPageControlsPayload payload; - AddPageControlsAction(this.payload); -} - -class ReplacePageControlsAction { - final ReplacePageControlsPayload payload; - ReplacePageControlsAction(this.payload); -} - -class PageControlsBatchAction { - final PageControlsBatchPayload payload; - PageControlsBatchAction(this.payload); -} - -class UpdateControlPropsAction { - final UpdateControlPropsPayload payload; - UpdateControlPropsAction(this.payload); -} - -class AppendControlPropsAction { - final AppendControlPropsPayload payload; - AppendControlPropsAction(this.payload); -} - -class CleanControlAction { - final CleanControlPayload payload; - CleanControlAction(this.payload); -} - -class RemoveControlAction { - final RemoveControlPayload payload; - RemoveControlAction(this.payload); -} diff --git a/packages/flet/lib/src/control_factory.dart b/packages/flet/lib/src/control_factory.dart deleted file mode 100644 index 03df38f03..000000000 --- a/packages/flet/lib/src/control_factory.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter/widgets.dart'; - -import 'flet_control_backend.dart'; -import 'models/control.dart'; - -class CreateControlArgs { - final Key? key; - final Control? parent; - final Control control; - final List children; - final Widget? nextChild; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - - CreateControlArgs(this.key, this.parent, this.control, this.children, - this.nextChild, this.parentDisabled, this.parentAdaptive, this.backend); -} - -typedef CreateControlFactory = Widget? Function(CreateControlArgs args); diff --git a/packages/flet/lib/src/controls/adaptive_alert_dialog.dart b/packages/flet/lib/src/controls/adaptive_alert_dialog.dart new file mode 100644 index 000000000..5918ed25e --- /dev/null +++ b/packages/flet/lib/src/controls/adaptive_alert_dialog.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +import '../controls/alert_dialog.dart'; +import '../models/control.dart'; +import '../widgets/flet_store_mixin.dart'; +import 'cupertino_alert_dialog.dart'; + +class AdaptiveAlertDialogControl extends StatelessWidget with FletStoreMixin { + final Control control; + + const AdaptiveAlertDialogControl({super.key, required this.control}); + + @override + Widget build(BuildContext context) { + debugPrint("AdaptiveAlertDialog build: ${control.id}"); + + return withPagePlatform((context, platform) { + if (control.adaptive == true && + (platform == TargetPlatform.iOS || + platform == TargetPlatform.macOS)) { + return CupertinoAlertDialogControl(control: control); + } else { + return AlertDialogControl(control: control); + } + }); + } +} diff --git a/packages/flet/lib/src/controls/adaptive_app_bar.dart b/packages/flet/lib/src/controls/adaptive_app_bar.dart new file mode 100644 index 000000000..de5350d72 --- /dev/null +++ b/packages/flet/lib/src/controls/adaptive_app_bar.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +import '../models/control.dart'; +import '../widgets/flet_store_mixin.dart'; +import 'app_bar.dart'; +import 'cupertino_app_bar.dart'; + +class AdaptiveAppBarControl extends StatelessWidget with FletStoreMixin { + final Control control; + + const AdaptiveAppBarControl({super.key, required this.control}); + + @override + Widget build(BuildContext context) { + debugPrint("AdaptiveAppBarControl build: ${control.id}"); + + return withPagePlatform((context, platform) { + if (control.adaptive == true && + (platform == TargetPlatform.iOS || + platform == TargetPlatform.macOS)) { + return CupertinoAppBarControl(control: control); + } else { + return AppBarControl(control: control); + } + }); + } +} diff --git a/packages/flet/lib/src/controls/adaptive_button.dart b/packages/flet/lib/src/controls/adaptive_button.dart new file mode 100644 index 000000000..3f5afc8b0 --- /dev/null +++ b/packages/flet/lib/src/controls/adaptive_button.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +import '../models/control.dart'; +import '../widgets/flet_store_mixin.dart'; +import 'button.dart'; +import 'cupertino_button.dart'; +import 'cupertino_dialog_action.dart'; + +class AdaptiveButtonControl extends StatelessWidget with FletStoreMixin { + final Control control; + + const AdaptiveButtonControl({super.key, required this.control}); + + @override + Widget build(BuildContext context) { + debugPrint("AdaptiveButton build: ${control.id}"); + + return withPagePlatform((context, platform) { + if (control.adaptive == true && + (platform == TargetPlatform.iOS || + platform == TargetPlatform.macOS)) { + return (control.parent?.type == "AlertDialog" || + control.parent?.type == "CupertinoAlertDialog") + ? CupertinoDialogActionControl( + control: control, + ) + : CupertinoButtonControl( + control: control, + ); + } else { + return ButtonControl(control: control); + } + }); + } +} diff --git a/packages/flet/lib/src/controls/adaptive_checkbox.dart b/packages/flet/lib/src/controls/adaptive_checkbox.dart new file mode 100644 index 000000000..b28972d74 --- /dev/null +++ b/packages/flet/lib/src/controls/adaptive_checkbox.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +import '../models/control.dart'; +import '../widgets/flet_store_mixin.dart'; +import 'checkbox.dart'; +import 'cupertino_checkbox.dart'; + +class AdaptiveCheckboxControl extends StatelessWidget with FletStoreMixin { + final Control control; + + const AdaptiveCheckboxControl({super.key, required this.control}); + + @override + Widget build(BuildContext context) { + debugPrint("AdaptiveCheckboxControl build: ${control.id}"); + + return withPagePlatform((context, platform) { + if (control.adaptive == true && + (platform == TargetPlatform.iOS || + platform == TargetPlatform.macOS)) { + return CupertinoCheckboxControl(control: control); + } else { + return CheckboxControl(control: control); + } + }); + } +} diff --git a/packages/flet/lib/src/controls/adaptive_radio.dart b/packages/flet/lib/src/controls/adaptive_radio.dart new file mode 100644 index 000000000..d7c43fb08 --- /dev/null +++ b/packages/flet/lib/src/controls/adaptive_radio.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +import '../models/control.dart'; +import '../widgets/flet_store_mixin.dart'; +import 'cupertino_radio.dart'; +import 'radio.dart'; + +class AdaptiveRadioControl extends StatelessWidget with FletStoreMixin { + final Control control; + + const AdaptiveRadioControl({super.key, required this.control}); + + @override + Widget build(BuildContext context) { + debugPrint("AdaptiveRadioControl build: ${control.id}"); + + return withPagePlatform((context, platform) { + if (control.adaptive == true && + (platform == TargetPlatform.iOS || + platform == TargetPlatform.macOS)) { + return CupertinoRadioControl(control: control); + } else { + return RadioControl(control: control); + } + }); + } +} diff --git a/packages/flet/lib/src/controls/adaptive_slider.dart b/packages/flet/lib/src/controls/adaptive_slider.dart new file mode 100644 index 000000000..add4377e1 --- /dev/null +++ b/packages/flet/lib/src/controls/adaptive_slider.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +import '../models/control.dart'; +import '../widgets/flet_store_mixin.dart'; +import 'cupertino_slider.dart'; +import 'slider.dart'; + +class AdaptiveSliderControl extends StatelessWidget with FletStoreMixin { + final Control control; + + const AdaptiveSliderControl({super.key, required this.control}); + + @override + Widget build(BuildContext context) { + debugPrint("AdaptiveSlider build: ${control.id}"); + + return withPagePlatform((context, platform) { + if (control.adaptive == true && + (platform == TargetPlatform.iOS || + platform == TargetPlatform.macOS)) { + return CupertinoSliderControl(control: control); + } else { + return SliderControl(control: control); + } + }); + } +} diff --git a/packages/flet/lib/src/controls/adaptive_switch.dart b/packages/flet/lib/src/controls/adaptive_switch.dart new file mode 100644 index 000000000..92d4f29da --- /dev/null +++ b/packages/flet/lib/src/controls/adaptive_switch.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +import '../models/control.dart'; +import '../widgets/flet_store_mixin.dart'; +import 'cupertino_switch.dart'; +import 'switch.dart'; + +class AdaptiveSwitchControl extends StatelessWidget with FletStoreMixin { + final Control control; + + const AdaptiveSwitchControl({super.key, required this.control}); + + @override + Widget build(BuildContext context) { + debugPrint("AdaptiveSwitch build: ${control.id}"); + + return withPagePlatform((context, platform) { + if (control.adaptive == true && + (platform == TargetPlatform.iOS || + platform == TargetPlatform.macOS)) { + return CupertinoSwitchControl(control: control); + } else { + return SwitchControl(control: control); + } + }); + } +} diff --git a/packages/flet/lib/src/controls/adaptive_texfield.dart b/packages/flet/lib/src/controls/adaptive_texfield.dart new file mode 100644 index 000000000..99244eb83 --- /dev/null +++ b/packages/flet/lib/src/controls/adaptive_texfield.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +import '../models/control.dart'; +import '../widgets/flet_store_mixin.dart'; +import 'cupertino_textfield.dart'; +import 'textfield.dart'; + +class AdaptiveTextFieldControl extends StatelessWidget with FletStoreMixin { + final Control control; + + const AdaptiveTextFieldControl({super.key, required this.control}); + + @override + Widget build(BuildContext context) { + debugPrint("AdaptiveTextFieldControl build: ${control.id}"); + + return withPagePlatform((context, platform) { + if (control.adaptive == true && + (platform == TargetPlatform.iOS || + platform == TargetPlatform.macOS)) { + return CupertinoTextFieldControl(control: control); + } else { + return TextFieldControl(control: control); + } + }); + } +} diff --git a/packages/flet/lib/src/controls/alert_dialog.dart b/packages/flet/lib/src/controls/alert_dialog.dart index 1019ab530..6e1cc648a 100644 --- a/packages/flet/lib/src/controls/alert_dialog.dart +++ b/packages/flet/lib/src/controls/alert_dialog.dart @@ -1,182 +1,157 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/alignment.dart'; import '../utils/borders.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; -import '../utils/others.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; import '../utils/text.dart'; -import 'create_control.dart'; -import 'cupertino_alert_dialog.dart'; -import 'error.dart'; -import 'flet_store_mixin.dart'; +import '../widgets/error.dart'; +import 'control_widget.dart'; class AlertDialogControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final Widget? nextChild; - final FletControlBackend backend; - - const AlertDialogControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.nextChild, - required this.backend}); + + AlertDialogControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _AlertDialogControlState(); } -class _AlertDialogControlState extends State - with FletStoreMixin { - Widget _createAlertDialog() { - bool disabled = widget.control.isDisabled || widget.parentDisabled; - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - var titleCtrls = - widget.children.where((c) => c.name == "title" && c.isVisible); - String titleStr = widget.control.attrString("title", "")!; - var iconCtrls = - widget.children.where((c) => c.name == "icon" && c.isVisible); - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); - var actionCtrls = - widget.children.where((c) => c.name == "action" && c.isVisible); - final actionsAlignment = - parseMainAxisAlignment(widget.control.attrString("actionsAlignment")); +class _AlertDialogControlState extends State { + Widget? _dialog; + bool _open = false; + NavigatorState? _navigatorState; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + debugPrint("AlertDialog.didChangeDependencies: ${widget.control.id}"); + _navigatorState = Navigator.of(context); + _toggleDialog(); + } + + @override + void didUpdateWidget(covariant AlertDialogControl oldWidget) { + debugPrint("AlertDialog.didUpdateWidget: ${widget.control.id}"); + super.didUpdateWidget(oldWidget); + _toggleDialog(); + } - if (titleCtrls.isEmpty && - titleStr == "" && - contentCtrls.isEmpty && - actionCtrls.isEmpty) { + @override + void dispose() { + debugPrint("AlertDialog.dispose: ${widget.control.id}"); + _closeDialog(); + super.dispose(); + } + + Widget _createAlertDialog() { + var title = widget.control.get("title"); + var content = widget.control.buildWidget("content"); + var actions = widget.control.buildWidgets("actions"); + if (title == null && content == null && actions.isEmpty) { return const ErrorControl( "AlertDialog has nothing to display. Provide at minimum one of the following: title, content, actions"); } + final actionsAlignment = + widget.control.getMainAxisAlignment("actions_alignment"); var clipBehavior = - parseClip(widget.control.attrString("clipBehavior"), Clip.none)!; + parseClip(widget.control.getString("clip_behavior"), Clip.none)!; return AlertDialog( - title: titleCtrls.isNotEmpty - ? createControl(widget.control, titleCtrls.first.id, disabled, - parentAdaptive: adaptive) - : titleStr != "" - ? Text(titleStr) + title: title is Control + ? ControlWidget(control: title) + : title is String + ? Text(title) : null, - titlePadding: parseEdgeInsets(widget.control, "titlePadding"), - content: contentCtrls.isNotEmpty - ? createControl(widget.control, contentCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null, - contentPadding: parseEdgeInsets(widget.control, "contentPadding", + titlePadding: widget.control.getPadding("title_padding"), + content: content, + contentPadding: widget.control.getPadding("content_padding", const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0))!, - actions: actionCtrls - .map((c) => createControl(widget.control, c.id, disabled, - parentAdaptive: adaptive)) - .toList(), - actionsPadding: parseEdgeInsets(widget.control, "actionsPadding"), + actions: actions, + actionsPadding: widget.control.getPadding("actions_padding"), actionsAlignment: actionsAlignment, - shape: parseOutlinedBorder(widget.control, "shape"), - semanticLabel: widget.control.attrString("semanticsLabel"), - insetPadding: parseEdgeInsets(widget.control, "insetPadding", + shape: widget.control.getShape("shape", Theme.of(context)), + semanticLabel: widget.control.getString("semantics_label"), + insetPadding: widget.control.getPadding("inset_padding", const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0))!, - iconPadding: parseEdgeInsets(widget.control, "iconPadding"), - backgroundColor: widget.control.attrColor("bgcolor", context), - buttonPadding: parseEdgeInsets(widget.control, "actionButtonPadding"), - surfaceTintColor: widget.control.attrColor("surfaceTintColor", context), - shadowColor: widget.control.attrColor("shadowColor", context), - elevation: widget.control.attrDouble("elevation"), + iconPadding: widget.control.getPadding("icon_padding"), + backgroundColor: widget.control.getColor("bgcolor", context), + buttonPadding: widget.control.getPadding("action_button_padding"), + surfaceTintColor: widget.control.getColor("surface_tint_color", context), + shadowColor: widget.control.getColor("shadow_color", context), + elevation: widget.control.getDouble("elevation"), clipBehavior: clipBehavior, - icon: iconCtrls.isNotEmpty - ? createControl(widget.control, iconCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null, - iconColor: widget.control.attrColor("iconColor", context), - scrollable: widget.control.attrBool("scrollable", false)!, + icon: widget.control.buildIconOrWidget("icon"), + iconColor: widget.control.getColor("icon_color", context), + scrollable: widget.control.getBool("scrollable", false)!, actionsOverflowButtonSpacing: - widget.control.attrDouble("actionsOverflowButtonSpacing"), - alignment: parseAlignment(widget.control, "alignment"), + widget.control.getDouble("actions_overflow_button_spacing"), + alignment: widget.control.getAlignment("alignment"), contentTextStyle: - parseTextStyle(Theme.of(context), widget.control, "contentTextStyle"), + widget.control.getTextStyle("content_text_style", Theme.of(context)), titleTextStyle: - parseTextStyle(Theme.of(context), widget.control, "titleTextStyle"), + widget.control.getTextStyle("title_text_style", Theme.of(context)), ); } - @override - Widget build(BuildContext context) { - debugPrint("AlertDialog build ($hashCode): ${widget.control.id}"); - - return withPagePlatform((context, platform) { - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - if (adaptive == true && - (platform == TargetPlatform.iOS || - platform == TargetPlatform.macOS)) { - return CupertinoAlertDialogControl( - control: widget.control, - parentDisabled: widget.parentDisabled, - children: widget.children, - nextChild: widget.nextChild, - parentAdaptive: adaptive, - backend: widget.backend); + void _toggleDialog() { + debugPrint("AlertDialog build: ${widget.control.id}"); + + var open = widget.control.getBool("open", false)!; + var modal = widget.control.getBool("modal", false)!; + + if (open && (open != _open)) { + _dialog = _createAlertDialog(); + + if (_dialog is ErrorControl) { + debugPrint( + "AlertDialog: ErrorControl, not showing dialog: ${(_dialog as ErrorControl).message}"); + return; } - bool lastOpen = widget.control.state["open"] ?? false; - - debugPrint("AlertDialog build: ${widget.control.id}"); - - var open = widget.control.attrBool("open", false)!; - var modal = widget.control.attrBool("modal", false)!; - - debugPrint("Current open state: $lastOpen"); - debugPrint("New open state: $open"); - - if (open && (open != lastOpen)) { - var dialog = _createAlertDialog(); - if (dialog is ErrorControl) { - return dialog; - } - - // close previous dialog - if (ModalRoute.of(context)?.isCurrent != true) { - Navigator.of(context).pop(); - } - - widget.control.state["open"] = open; - - WidgetsBinding.instance.addPostFrameCallback((_) { - showDialog( - barrierDismissible: !modal, - barrierColor: widget.control.attrColor("barrierColor", context), - useRootNavigator: false, - context: context, - builder: (context) => _createAlertDialog()).then((value) { - lastOpen = widget.control.state["open"] ?? false; - debugPrint("Dialog should be dismissed ($hashCode): $lastOpen"); - bool shouldDismiss = lastOpen; - widget.control.state["open"] = false; - - if (shouldDismiss) { - widget.backend - .updateControlState(widget.control.id, {"open": "false"}); - widget.backend.triggerControlEvent(widget.control.id, "dismiss"); - } - }); + _open = open; + + WidgetsBinding.instance.addPostFrameCallback((_) { + showDialog( + barrierDismissible: !modal, + barrierColor: widget.control.getColor("barrierColor", context), + useRootNavigator: false, + context: context, + builder: (context) => _dialog!).then((value) { + debugPrint("Dismissing AlertDialog(${widget.control.id})"); + _open = false; + widget.control.updateProperties({"open": false}); + widget.control.triggerEvent("dismiss"); }); - } else if (open != lastOpen && lastOpen) { - Navigator.of(context).pop(); - } + }); + } else if (!open && _open) { + _closeDialog(); + } + } - return widget.nextChild ?? const SizedBox.shrink(); - }); + @override + Widget build(BuildContext context) { + return _dialog is ErrorControl ? _dialog! : const SizedBox.shrink(); + } + + void _closeDialog() { + if (_open) { + if (_navigatorState?.canPop() == true) { + debugPrint( + "AlertDialog(${widget.control.id}): Closing dialog managed by this widget."); + _navigatorState?.pop(); + _open = false; + _dialog = null; + } else { + debugPrint( + "AlertDialog(${widget.control.id}): Dialog was not opened by this widget, skipping pop."); + } + } } } diff --git a/packages/flet/lib/src/controls/animated_switcher.dart b/packages/flet/lib/src/controls/animated_switcher.dart index e4344f6ce..af6d90964 100644 --- a/packages/flet/lib/src/controls/animated_switcher.dart +++ b/packages/flet/lib/src/controls/animated_switcher.dart @@ -1,67 +1,52 @@ import 'package:flutter/material.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/animations.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/numbers.dart'; +import '../utils/time.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; class AnimatedSwitcherControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - const AnimatedSwitcherControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive}); + const AnimatedSwitcherControl({ + super.key, + required this.control, + }); @override Widget build(BuildContext context) { debugPrint("AnimatedSwitcher build: ${control.id}"); - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); + var content = + control.buildWidget("content", notifyParent: true, key: UniqueKey()); - var switchInCurve = - parseCurve(control.attrString("switchInCurve"), Curves.linear)!; - var switchOutCurve = - parseCurve(control.attrString("switchOutCurve"), Curves.linear)!; - var duration = control.attrInt("duration", 1000)!; - var reverseDuration = control.attrInt("reverseDuration", 1000)!; - bool disabled = control.isDisabled || parentDisabled; - - if (contentCtrls.isEmpty) { + if (content == null) { return const ErrorControl( "AnimatedSwitcher.content must be provided and visible"); } - - var child = createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: parentAdaptive); - - return constrainedControl( - context, - AnimatedSwitcher( - duration: Duration(milliseconds: duration), - reverseDuration: Duration(milliseconds: reverseDuration), - switchInCurve: switchInCurve, - switchOutCurve: switchOutCurve, - transitionBuilder: (child, animation) { - switch (control.attrString("transition", "")!.toLowerCase()) { - case "rotation": - return RotationTransition(turns: animation, child: child); - case "scale": - return ScaleTransition(scale: animation, child: child); - default: - return FadeTransition(opacity: animation, child: child); - } - }, - child: child), - parent, - control); + final animatedSwitcher = AnimatedSwitcher( + duration: control.getDuration("duration", const Duration(seconds: 1))!, + reverseDuration: parseDuration( + control.get("reverse_duration"), const Duration(seconds: 1))!, + switchInCurve: + parseCurve(control.getString("switch_in_curve"), Curves.linear)!, + switchOutCurve: + parseCurve(control.getString("switch_out_curve"), Curves.linear)!, + transitionBuilder: (child, animation) { + switch (control.getString("transition")?.toLowerCase()) { + case "rotation": + return RotationTransition(turns: animation, child: child); + case "scale": + return ScaleTransition(scale: animation, child: child); + default: + return FadeTransition(opacity: animation, child: child); + } + }, + child: content, + ); + return ConstrainedControl(control: control, child: animatedSwitcher); } } diff --git a/packages/flet/lib/src/controls/app_bar.dart b/packages/flet/lib/src/controls/app_bar.dart index 012343819..b24abe6ab 100644 --- a/packages/flet/lib/src/controls/app_bar.dart +++ b/packages/flet/lib/src/controls/app_bar.dart @@ -1,103 +1,61 @@ import 'package:flutter/material.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/borders.dart'; -import '../utils/edge_insets.dart'; -import '../utils/others.dart'; +import '../utils/colors.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; import '../utils/text.dart'; import '../utils/theme.dart'; -import 'create_control.dart'; -import 'cupertino_app_bar.dart'; -import 'flet_store_mixin.dart'; +import 'base_controls.dart'; -class AppBarControl extends StatelessWidget - with FletStoreMixin - implements PreferredSizeWidget { - final Control? parent; +class AppBarControl extends StatelessWidget implements PreferredSizeWidget { final Control control; - final bool parentDisabled; - final bool? parentAdaptive; - final List children; - final double height; - const AppBarControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.height}); + const AppBarControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("AppBar build: ${control.id}"); - return withPagePlatform((context, platform) { - bool? adaptive = control.attrBool("adaptive") ?? parentAdaptive; - if (adaptive == true && - (platform == TargetPlatform.iOS || - platform == TargetPlatform.macOS)) { - return CupertinoAppBarControl( - control: control, - parentDisabled: parentDisabled, - parentAdaptive: adaptive, - children: children); - } - - var leadingCtrls = - children.where((c) => c.name == "leading" && c.isVisible); - var titleCtrls = children.where((c) => c.name == "title" && c.isVisible); - var actionCtrls = - children.where((c) => c.name == "action" && c.isVisible); - var isSecondary = control.attrBool("isSecondary", false)!; - - var appBar = AppBar( - leading: leadingCtrls.isNotEmpty - ? createControl(control, leadingCtrls.first.id, control.isDisabled, - parentAdaptive: adaptive) - : null, - leadingWidth: control.attrDouble("leadingWidth"), - automaticallyImplyLeading: - control.attrBool("automaticallyImplyLeading", true)!, - title: titleCtrls.isNotEmpty - ? createControl(control, titleCtrls.first.id, control.isDisabled, - parentAdaptive: adaptive) - : null, - centerTitle: control.attrBool("centerTitle", false)!, - toolbarHeight: preferredSize.height, - foregroundColor: control.attrColor("color", context), - backgroundColor: control.attrColor("bgcolor", context), - elevation: control.attrDouble("elevation"), - actions: actionCtrls - .map((c) => createControl(control, c.id, control.isDisabled, - parentAdaptive: adaptive)) - .toList(), - systemOverlayStyle: Theme.of(context) - .extension() - ?.systemUiOverlayStyle, - shadowColor: control.attrColor("shadowColor", context), - surfaceTintColor: control.attrColor("surfaceTintColor", context), - scrolledUnderElevation: control.attrDouble("elevationOnScroll"), - forceMaterialTransparency: - control.attrBool("forceMaterialTransparency", false)!, - primary: !isSecondary, - titleSpacing: control.attrDouble("titleSpacing"), - excludeHeaderSemantics: - control.attrBool("excludeHeaderSemantics", false)!, - clipBehavior: parseClip(control.attrString("clipBehavior")), - titleTextStyle: - parseTextStyle(Theme.of(context), control, "titleTextStyle"), - shape: parseOutlinedBorder(control, "shape"), - toolbarOpacity: control.attrDouble("toolbarOpacity", 1)!, - toolbarTextStyle: - parseTextStyle(Theme.of(context), control, "toolbarTextStyle"), - actionsPadding: parseEdgeInsets(control, "actionsPadding"), - ); - return baseControl(context, appBar, parent, control); - }); + var appBar = AppBar( + leading: control.buildWidget("leading"), + leadingWidth: control.getDouble("leading_width"), + automaticallyImplyLeading: + control.getBool("automatically_imply_leading", true)!, + title: control.buildTextOrWidget("title"), + centerTitle: control.getBool("center_title", false)!, + toolbarHeight: preferredSize.height, + foregroundColor: control.getColor("color", context), + backgroundColor: control.getColor("bgcolor", context), + elevation: control.getDouble("elevation"), + actions: control.buildWidgets("actions"), + systemOverlayStyle: Theme.of(context) + .extension() + ?.systemUiOverlayStyle, + shadowColor: control.getColor("shadow_color", context), + surfaceTintColor: control.getColor("surface_tint_color", context), + scrolledUnderElevation: control.getDouble("elevation_on_scroll"), + forceMaterialTransparency: + control.getBool("force_material_transparency", false)!, + primary: !control.getBool("isSecondary", false)!, + titleSpacing: control.getDouble("title_spacing"), + excludeHeaderSemantics: + control.getBool("exclude_header_semantics", false)!, + clipBehavior: control.getClipBehavior("clip_behavior"), + titleTextStyle: + control.getTextStyle("title_text_style", Theme.of(context)), + shape: control.getShape("shape", Theme.of(context)), + toolbarOpacity: control.getDouble("toolbar_opacity", 1)!, + toolbarTextStyle: + parseTextStyle(control.get("toolbar_text_style"), Theme.of(context)), + ); + + return BaseControl(control: control, child: appBar); } @override - Size get preferredSize => Size.fromHeight(height); + Size get preferredSize => + Size.fromHeight(control.getDouble("toolbar_height", kToolbarHeight)!); } diff --git a/packages/flet/lib/src/controls/auto_complete.dart b/packages/flet/lib/src/controls/auto_complete.dart index 1175fc375..4e225403b 100644 --- a/packages/flet/lib/src/controls/auto_complete.dart +++ b/packages/flet/lib/src/controls/auto_complete.dart @@ -1,41 +1,28 @@ -import 'dart:convert'; - +import 'package:flet/src/utils/numbers.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; import '../utils/auto_complete.dart'; -import 'create_control.dart'; +import 'base_controls.dart'; class AutoCompleteControl extends StatelessWidget { - final Control? parent; final Control control; - final FletControlBackend backend; - const AutoCompleteControl( - {super.key, - required this.parent, - required this.control, - required this.backend}); + const AutoCompleteControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("AutoComplete build: ${control.id}"); - var suggestionsMaxHeight = control.attrDouble("suggestionsMaxHeight", 200)!; - var suggestions = parseAutoCompleteSuggestions(control, "suggestions"); + var suggestions = + parseAutoCompleteSuggestions(control.get("suggestions"), const [])!; - var auto = Autocomplete( - optionsMaxHeight: suggestionsMaxHeight, + var autoComplete = Autocomplete( + optionsMaxHeight: control.getDouble("suggestions_max_height", 200)!, onSelected: (AutoCompleteSuggestion selection) { - backend.updateControlState(control.id, - {"selectedIndex": suggestions.indexOf(selection).toString()}); - backend.triggerControlEvent( - control.id, - "select", - json.encode(AutoCompleteSuggestion( - key: selection.key, value: selection.value) - .toJson())); + control.updateProperties( + {"_selected_index": suggestions.indexOf(selection)}); + control.triggerEvent("select", {"selection": selection.toMap()}); }, // optionsViewBuilder: optionsViewBuilder, optionsBuilder: (TextEditingValue textEditingValue) { @@ -51,6 +38,6 @@ class AutoCompleteControl extends StatelessWidget { }, ); - return baseControl(context, auto, parent, control); + return BaseControl(control: control, child: autoComplete); } } diff --git a/packages/flet/lib/src/controls/autofill_group.dart b/packages/flet/lib/src/controls/autofill_group.dart index bb925a0e0..07d6a5048 100644 --- a/packages/flet/lib/src/controls/autofill_group.dart +++ b/packages/flet/lib/src/controls/autofill_group.dart @@ -1,41 +1,28 @@ -import 'package:flet/src/utils/autofill.dart'; import 'package:flutter/material.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/autofill.dart'; +import '../widgets/error.dart'; class AutofillGroupControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - const AutofillGroupControl( - {super.key, - required this.parent, - required this.control, - required this.children, - required this.parentDisabled, - this.parentAdaptive}); + const AutofillGroupControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("AutofillGroup build: ${control.id}"); - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - bool disabled = control.isDisabled || parentDisabled; + var content = control.buildWidget("content"); - if (contentCtrls.isEmpty) { + if (content == null) { return const ErrorControl("AutofillGroup control has no content."); } return AutofillGroup( - onDisposeAction: parseAutofillContextAction( - control.attrString("disposeAction"), AutofillContextAction.commit)!, - child: createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: parentAdaptive)); + onDisposeAction: control.getAutofillContextAction( + "dispose_action", AutofillContextAction.commit)!, + child: content); } } diff --git a/packages/flet/lib/src/controls/banner.dart b/packages/flet/lib/src/controls/banner.dart index 1eae443c1..810a7771c 100644 --- a/packages/flet/lib/src/controls/banner.dart +++ b/packages/flet/lib/src/controls/banner.dart @@ -1,112 +1,132 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; +import '../utils/numbers.dart'; import '../utils/text.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../widgets/error.dart'; class BannerControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final Widget? nextChild; - final FletControlBackend backend; - - const BannerControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.nextChild, - required this.backend}); + + BannerControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _BannerControlState(); } class _BannerControlState extends State { - bool _open = false; + Widget? _dialog; + + @override + void initState() { + super.initState(); + debugPrint("Banner.initState: ${widget.control.id}"); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + debugPrint("Banner.didChangeDependencies: ${widget.control.id}"); + _toggleBanner(); + } + + @override + void didUpdateWidget(covariant BannerControl oldWidget) { + debugPrint("Banner.didUpdateWidget: ${widget.control.id}"); + super.didUpdateWidget(oldWidget); + _toggleBanner(); + } Widget _createBanner() { - bool disabled = widget.control.isDisabled || widget.parentDisabled; - var leadingCtrls = - widget.children.where((c) => c.name == "leading" && c.isVisible); - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); - var actionCtrls = - widget.children.where((c) => c.name == "action" && c.isVisible); - - if (contentCtrls.isEmpty) { + var leading = widget.control.buildIconOrWidget("leading"); + var content = widget.control.buildTextOrWidget("content"); + var actions = widget.control.buildWidgets("actions"); + + if (content == null) { return const ErrorControl("Banner.content must be provided and visible"); - } else if (actionCtrls.isEmpty) { + } else if (actions.isEmpty) { return const ErrorControl( "Banner.actions must be provided and at least one action should be visible"); } return MaterialBanner( - leading: leadingCtrls.isNotEmpty - ? createControl(widget.control, leadingCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : null, - leadingPadding: parseEdgeInsets(widget.control, "leadingPadding"), - content: createControl(widget.control, contentCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive), - padding: parseEdgeInsets(widget.control, "contentPadding"), - actions: actionCtrls - .map((c) => createControl(widget.control, c.id, disabled, - parentAdaptive: widget.parentAdaptive)) - .toList(), - forceActionsBelow: widget.control.attrBool("forceActionsBelow", false)!, - backgroundColor: widget.control.attrColor("bgcolor", context), + leading: leading, + leadingPadding: widget.control.getPadding("leading_padding"), + content: content, + padding: widget.control.getPadding("content_padding"), + actions: actions, + forceActionsBelow: widget.control.getBool("force_actions_below", false)!, + backgroundColor: widget.control.getColor("bgcolor", context), contentTextStyle: - parseTextStyle(Theme.of(context), widget.control, "contentTextStyle"), - surfaceTintColor: widget.control.attrColor("surfaceTintColor", context), - shadowColor: widget.control.attrColor("shadowColor", context), - dividerColor: widget.control.attrColor("dividerColor", context), - elevation: widget.control.attrDouble("elevation"), + widget.control.getTextStyle("content_text_style", Theme.of(context)), + surfaceTintColor: widget.control.getColor("surface_tint_color", context), + shadowColor: widget.control.getColor("shadow_color", context), + dividerColor: widget.control.getColor("divider_color", context), + elevation: widget.control.getDouble("elevation"), minActionBarHeight: - widget.control.attrDouble("minActionBarHeight", 52.0)!, - margin: parseEdgeInsets(widget.control, "margin"), + widget.control.getDouble("min_action_bar_height", 52.0)!, + margin: widget.control.getMargin("margin"), onVisible: () { - widget.backend.triggerControlEvent(widget.control.id, "visible"); + widget.control.triggerEvent("visible"); }, ); } - @override - Widget build(BuildContext context) { - debugPrint("Banner build: ${widget.control.id}"); + void _toggleBanner() { + var dismissed = widget.control.get("_dismissed"); + + debugPrint("Banner build: ${widget.control.id}, _dismissed=$dismissed"); - debugPrint("Banner build: ${widget.control.id}"); + if (dismissed == true) return; - var open = widget.control.attrBool("open", false)!; + final lastOpen = widget.control.getBool("_open", false)!; + var open = widget.control.getBool("open", false)!; - if (open && (open != _open)) { - var banner = _createBanner(); - if (banner is ErrorControl) { - return banner; + if (open && (open != lastOpen)) { + _dialog = _createBanner(); + + if (_dialog is ErrorControl) { + debugPrint( + "Banner: ErrorControl, not showing dialog: ${(_dialog as ErrorControl).message}"); + return; } + widget.control.updateProperties({"_open": open}, python: false); + WidgetsBinding.instance.addPostFrameCallback((_) { ScaffoldMessenger.of(context).removeCurrentMaterialBanner(); - ScaffoldMessenger.of(context) - .showMaterialBanner(banner as MaterialBanner); + .showMaterialBanner(_dialog as MaterialBanner) + .closed + .then((reason) { + debugPrint( + "Closing Banner(${widget.control.id}) with reason: $reason"); + if (widget.control.get("_dismissed") != true) { + widget.control + .updateProperties({"_dismissed": true}, python: false); + debugPrint( + "Dismissing Banner(${widget.control.id}) with reason: $reason"); + //_open = false; + widget.control.updateProperties({"_open": false}, python: false); + widget.control.updateProperties({"open": false}); + widget.control.triggerEvent("dismiss"); + } + }); }); - } else if (open != _open && _open) { + } else if (!open && lastOpen) { WidgetsBinding.instance.addPostFrameCallback((_) { - ScaffoldMessenger.of(context).removeCurrentMaterialBanner(); + ScaffoldMessenger.of(context).hideCurrentMaterialBanner(); + widget.control.updateProperties({"_open": false}, python: false); }); } + } - _open = open; - - return widget.nextChild ?? const SizedBox.shrink(); + @override + Widget build(BuildContext context) { + return _dialog is ErrorControl ? _dialog! : const SizedBox.shrink(); } } diff --git a/packages/flet/lib/src/controls/barchart.dart b/packages/flet/lib/src/controls/barchart.dart deleted file mode 100644 index 2c2e86ead..000000000 --- a/packages/flet/lib/src/controls/barchart.dart +++ /dev/null @@ -1,425 +0,0 @@ -import 'dart:convert'; - -import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_redux/flutter_redux.dart'; -import 'package:redux/redux.dart'; - -import '../flet_control_backend.dart'; -import '../models/app_state.dart'; -import '../models/control.dart'; -import '../utils/animations.dart'; -import '../utils/borders.dart'; -import '../utils/charts.dart'; -import '../utils/edge_insets.dart'; -import '../utils/gradient.dart'; -import '../utils/text.dart'; -import 'charts.dart'; -import 'create_control.dart'; - -TooltipDirection? parseTooltipDirection(String? value, - [TooltipDirection? defValue]) { - if (value == null) { - return defValue; - } - return TooltipDirection.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -class BarChartEventData extends Equatable { - final String eventType; - final int? groupIndex; - final int? rodIndex; - final int? stackItemIndex; - - const BarChartEventData( - {required this.eventType, - required this.groupIndex, - required this.rodIndex, - required this.stackItemIndex}); - - Map toJson() => { - 'type': eventType, - 'group_index': groupIndex, - 'rod_index': rodIndex, - 'stack_item_index': stackItemIndex - }; - - @override - List get props => [eventType, groupIndex, rodIndex, stackItemIndex]; -} - -class BarChartGroupViewModel extends Equatable { - final Control control; - final List barRods; - - const BarChartGroupViewModel({required this.control, required this.barRods}); - - static BarChartGroupViewModel fromStore( - Store store, Control control) { - return BarChartGroupViewModel( - control: control, - barRods: store.state.controls[control.id]!.childIds - .map((childId) => store.state.controls[childId]) - .nonNulls - .where((c) => c.isVisible) - .map((c) => BarChartRodViewModel.fromStore(store, c)) - .toList()); - } - - @override - List get props => [control, barRods]; -} - -class BarChartRodStackItemViewModel extends Equatable { - final Control control; - - const BarChartRodStackItemViewModel({required this.control}); - - static BarChartRodStackItemViewModel fromStore( - Store store, Control control) { - return BarChartRodStackItemViewModel(control: control); - } - - @override - List get props => [control]; -} - -class BarChartRodViewModel extends Equatable { - final Control control; - final List rodStackItems; - - const BarChartRodViewModel( - {required this.control, required this.rodStackItems}); - - static BarChartRodViewModel fromStore( - Store store, Control control) { - return BarChartRodViewModel( - control: control, - rodStackItems: store.state.controls[control.id]!.childIds - .map((childId) => store.state.controls[childId]) - .nonNulls - .where((c) => c.isVisible) - .map((c) => BarChartRodStackItemViewModel.fromStore(store, c)) - .toList()); - } - - @override - List get props => [control, rodStackItems]; -} - -class BarChartViewModel extends Equatable { - final Control control; - final ChartAxisViewModel? leftAxis; - final ChartAxisViewModel? topAxis; - final ChartAxisViewModel? rightAxis; - final ChartAxisViewModel? bottomAxis; - final List barGroups; - - const BarChartViewModel( - {required this.control, - required this.leftAxis, - required this.topAxis, - required this.rightAxis, - required this.bottomAxis, - required this.barGroups}); - - static BarChartViewModel fromStore( - Store store, Control control, List children) { - var leftAxisCtrls = - children.where((c) => c.type == "axis" && c.name == "l" && c.isVisible); - var topAxisCtrls = - children.where((c) => c.type == "axis" && c.name == "t" && c.isVisible); - var rightAxisCtrls = - children.where((c) => c.type == "axis" && c.name == "r" && c.isVisible); - var bottomAxisCtrls = - children.where((c) => c.type == "axis" && c.name == "b" && c.isVisible); - return BarChartViewModel( - control: control, - leftAxis: leftAxisCtrls.isNotEmpty - ? ChartAxisViewModel.fromStore(store, leftAxisCtrls.first) - : null, - topAxis: topAxisCtrls.isNotEmpty - ? ChartAxisViewModel.fromStore(store, topAxisCtrls.first) - : null, - rightAxis: rightAxisCtrls.isNotEmpty - ? ChartAxisViewModel.fromStore(store, rightAxisCtrls.first) - : null, - bottomAxis: bottomAxisCtrls.isNotEmpty - ? ChartAxisViewModel.fromStore(store, bottomAxisCtrls.first) - : null, - barGroups: children - .where((c) => c.type == "group" && c.isVisible) - .map((c) => BarChartGroupViewModel.fromStore(store, c)) - .toList()); - } - - @override - List get props => - [control, leftAxis, rightAxis, topAxis, bottomAxis, barGroups]; -} - -class BarChartControl extends StatefulWidget { - final Control? parent; - final Control control; - final List children; - final bool parentDisabled; - final FletControlBackend backend; - - const BarChartControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.backend}); - - @override - State createState() => _BarChartControlState(); -} - -class _BarChartControlState extends State { - BarChartEventData? _eventData; - - @override - Widget build(BuildContext context) { - debugPrint("BarChart build: ${widget.control.id}"); - - var animate = parseAnimation(widget.control, "animate"); - var border = parseBorder(Theme.of(context), widget.control, "border"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - var result = StoreConnector( - distinct: true, - converter: (store) => - BarChartViewModel.fromStore(store, widget.control, widget.children), - builder: (context, viewModel) { - var leftTitles = - getAxisTitles(widget.control, viewModel.leftAxis, disabled); - var topTitles = - getAxisTitles(widget.control, viewModel.topAxis, disabled); - var rightTitles = - getAxisTitles(widget.control, viewModel.rightAxis, disabled); - var bottomTitles = - getAxisTitles(widget.control, viewModel.bottomAxis, disabled); - - var interactive = viewModel.control.attrBool("interactive", true)!; - - List barGroups = viewModel.barGroups - .map((g) => getGroupData( - Theme.of(context), widget.control, interactive, g)) - .toList(); - - var chart = BarChart( - BarChartData( - backgroundColor: widget.control.attrColor("bgcolor", context), - minY: widget.control.attrDouble("miny"), - maxY: widget.control.attrDouble("maxy"), - baselineY: widget.control.attrDouble("baseliney"), - titlesData: (leftTitles.sideTitles.showTitles || - topTitles.sideTitles.showTitles || - rightTitles.sideTitles.showTitles || - bottomTitles.sideTitles.showTitles) - ? FlTitlesData( - show: true, - leftTitles: leftTitles, - topTitles: topTitles, - rightTitles: rightTitles, - bottomTitles: bottomTitles, - ) - : const FlTitlesData(show: false), - borderData: border != null - ? FlBorderData(show: true, border: border) - : FlBorderData(show: false), - gridData: parseChartGridData(Theme.of(context), widget.control, - "horizontalGridLines", "verticalGridLines"), - groupsSpace: widget.control.attrDouble("groupsSpace"), - barTouchData: BarTouchData( - enabled: interactive, - touchTooltipData: BarTouchTooltipData( - getTooltipColor: (BarChartGroupData group) => widget.control - .attrColor("tooltipBgColor", context, - Theme.of(context).colorScheme.secondary)!, - tooltipRoundedRadius: - widget.control.attrDouble("tooltipRoundedRadius"), - tooltipMargin: widget.control.attrDouble("tooltipMargin"), - tooltipPadding: - parseEdgeInsets(widget.control, "tooltipPadding"), - maxContentWidth: widget.control.attrDouble("tooltipMaxWidth"), - rotateAngle: widget.control.attrDouble("tooltipRotateAngle"), - tooltipHorizontalOffset: - widget.control.attrDouble("tooltipHorizontalOffset"), - tooltipBorder: parseBorderSide( - Theme.of(context), widget.control, "tooltipBorderSide"), - fitInsideHorizontally: - widget.control.attrBool("tooltipFitInsideHorizontally"), - fitInsideVertically: - widget.control.attrBool("tooltipFitInsideVertically"), - direction: parseTooltipDirection( - widget.control.attrString("tooltipDirection")), - getTooltipItem: (group, groupIndex, rod, rodIndex) { - var dp = viewModel.barGroups[groupIndex].barRods[rodIndex]; - - var tooltip = dp.control.attrString("tooltip", - dp.control.attrDouble("toY", 0)!.toString())!; - var tooltipStyle = parseTextStyle( - Theme.of(context), dp.control, "tooltipStyle"); - tooltipStyle ??= const TextStyle(); - if (tooltipStyle.color == null) { - tooltipStyle = tooltipStyle.copyWith( - color: rod.gradient?.colors.first ?? - rod.color ?? - Colors.blueGrey); - } - TextAlign? tooltipAlign = parseTextAlign( - dp.control.attrString("tooltipAlign", ""), - TextAlign.center)!; - return dp.control.attrBool("showTooltip", true)! - ? BarTooltipItem(tooltip, tooltipStyle, - textAlign: tooltipAlign) - : null; - }, - ), - touchCallback: widget.control.attrBool("onChartEvent", false)! - ? (evt, resp) { - var eventData = resp != null && resp.spot != null - ? BarChartEventData( - eventType: evt.runtimeType - .toString() - .substring(2), // remove "Fl" - groupIndex: resp.spot!.touchedBarGroupIndex, - rodIndex: resp.spot!.touchedRodDataIndex, - stackItemIndex: - resp.spot!.touchedStackItemIndex) - : BarChartEventData( - eventType: evt.runtimeType - .toString() - .substring(2), // remove "Fl" - groupIndex: null, - rodIndex: null, - stackItemIndex: null); - if (eventData != _eventData) { - _eventData = eventData; - debugPrint( - "BarChart ${widget.control.id} ${eventData.eventType}"); - widget.backend.triggerControlEvent(widget.control.id, - "chart_event", json.encode(eventData)); - } - } - : null, - ), - barGroups: barGroups, - ), - swapAnimationDuration: animate != null - ? animate.duration - : const Duration(milliseconds: 150), // Optional - swapAnimationCurve: animate != null ? animate.curve : Curves.linear, - ); - - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return (constraints.maxHeight == double.infinity) - ? ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 300), - child: chart, - ) - : chart; - }); - }); - - return constrainedControl(context, result, widget.parent, widget.control); - } - - BarChartGroupData getGroupData(ThemeData theme, Control parent, - bool interactiveChart, BarChartGroupViewModel groupViewModel) { - return BarChartGroupData( - x: groupViewModel.control.attrInt("x", 0)!, - barsSpace: groupViewModel.control.attrDouble("barsSpace"), - groupVertically: groupViewModel.control.attrBool("groupVertically"), - showingTooltipIndicators: groupViewModel.barRods - .asMap() - .entries - .where((e) => - !interactiveChart && e.value.control.attrBool("selected", false)!) - .map((e) => e.key) - .toList(), - barRods: groupViewModel.barRods - .map((r) => - getRodData(theme, groupViewModel.control, interactiveChart, r)) - .toList(), - ); - } - - BarChartRodData getRodData(ThemeData theme, Control parent, - bool interactiveChart, BarChartRodViewModel rodViewModel) { - var bgFromY = rodViewModel.control.attrDouble("bgFromY"); - var bgToY = rodViewModel.control.attrDouble("bgToY"); - var bgColor = rodViewModel.control.attrColor("bgColor", context); - var bgGradient = parseGradient(theme, rodViewModel.control, "bgGradient"); - - return BarChartRodData( - fromY: rodViewModel.control.attrDouble("fromY"), - toY: rodViewModel.control.attrDouble("toY", 0)!, - width: rodViewModel.control.attrDouble("width"), - color: rodViewModel.control.attrColor("color", context), - gradient: parseGradient(theme, rodViewModel.control, "gradient"), - borderRadius: parseBorderRadius(rodViewModel.control, "borderRadius"), - borderSide: - parseBorderSide(theme, rodViewModel.control, "borderSide") ?? - BorderSide.none, - backDrawRodData: bgFromY != null || - bgToY != null || - bgColor != null || - bgGradient != null - ? BackgroundBarChartRodData( - show: true, - fromY: bgFromY, - toY: bgToY, - color: bgColor, - gradient: bgGradient) - : null, - rodStackItems: rodViewModel.rodStackItems - .map((item) => getRodStackItem( - theme, rodViewModel.control, interactiveChart, item)) - .toList()); - } - - BarChartRodStackItem getRodStackItem(ThemeData theme, Control parent, - bool interactiveChart, BarChartRodStackItemViewModel stackItemViewModel) { - return BarChartRodStackItem( - stackItemViewModel.control.attrDouble("fromY")!, - stackItemViewModel.control.attrDouble("toY", 0)!, - stackItemViewModel.control.attrColor("color", context)!, - parseBorderSide(theme, stackItemViewModel.control, "borderSide") ?? - BorderSide.none); - } - - AxisTitles getAxisTitles( - Control parent, ChartAxisViewModel? axisViewModel, bool disabled) { - if (axisViewModel == null) { - return const AxisTitles(sideTitles: SideTitles(showTitles: false)); - } - - return AxisTitles( - axisNameWidget: axisViewModel.title != null - ? createControl(parent, axisViewModel.title!.id, disabled) - : null, - axisNameSize: axisViewModel.control.attrDouble("titleSize") ?? 16, - sideTitles: SideTitles( - showTitles: axisViewModel.control.attrBool("showLabels", true)!, - reservedSize: axisViewModel.control.attrDouble("labelsSize") ?? 22, - interval: axisViewModel.control.attrDouble("labelsInterval"), - getTitlesWidget: axisViewModel.labels.isEmpty - ? defaultGetTitle - : (value, meta) { - return axisViewModel.labels.containsKey(value) - ? createControl( - parent, axisViewModel.labels[value]!.id, disabled) - : const SizedBox.shrink(); - }, - )); - } -} diff --git a/packages/flet/lib/src/controls/base_controls.dart b/packages/flet/lib/src/controls/base_controls.dart new file mode 100644 index 000000000..9c29aaaa2 --- /dev/null +++ b/packages/flet/lib/src/controls/base_controls.dart @@ -0,0 +1,264 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../extensions/control.dart'; +import '../models/control.dart'; +import '../utils/animations.dart'; +import '../utils/badge.dart'; +import '../utils/numbers.dart'; +import '../utils/tooltip.dart'; +import '../utils/transforms.dart'; +import '../widgets/error.dart'; + +class BaseControl extends StatelessWidget { + final Control control; + final Widget child; + + const BaseControl({super.key, required this.control, required this.child}); + + @override + Widget build(BuildContext context) { + Widget w = _opacity(context, child, control); + w = _tooltip(context, w, control); + w = _directionality(w, control); + return _expandable(w, control); + } +} + +class ConstrainedControl extends StatelessWidget { + final Control control; + final Widget child; + + const ConstrainedControl({ + super.key, + required this.control, + required this.child, + }); + + @override + Widget build(BuildContext context) { + Widget w = BaseControl(control: control, child: child); + + w = _sizedControl(w, control); + w = _rotatedControl(context, w, control); + w = _scaledControl(context, w, control); + w = _offsetControl(context, w, control); + w = _aspectRatio(w, control); + w = _positionedControl(context, w, control); + w = _badge(w, Theme.of(context), control); + + return w; + } +} + +Widget _tooltip(BuildContext context, Widget widget, Control control) { + final skipProps = control.internals?["skip_properties"] as List?; + if (skipProps?.contains("tooltip") == true) return widget; + + return parseTooltip(control.get("tooltip"), context, widget) ?? widget; +} + +Widget _badge(Widget widget, ThemeData theme, Control control) { + final skipProps = control.internals?["skip_properties"] as List?; + if (skipProps?.contains("badge") == true) return widget; + + return control.wrapWithBadge("badge", widget, theme); +} + +Widget _aspectRatio(Widget widget, Control control) { + var aspectRatio = control.getDouble("aspect_ratio"); + return aspectRatio != null + ? AspectRatio(aspectRatio: aspectRatio, child: widget) + : widget; +} + +Widget _directionality(Widget widget, Control control) { + bool rtl = control.getBool("rtl", false)!; + return rtl + ? Directionality(textDirection: TextDirection.rtl, child: widget) + : widget; +} + +Widget _expandable(Widget widget, Control control) { + var parent = control.parent; + if (parent != null && ["View", "Column", "Row"].contains(parent.type)) { + int? expand = control.properties.containsKey("expand") + ? control.get("expand") == true + ? 1 + : control.get("expand") == false + ? 0 + : control.getInt("expand") + : null; + var expandLoose = control.getBool("expand_loose"); + return expand != null + ? (expandLoose == true) + ? Flexible(flex: expand, child: widget) + : Expanded(flex: expand, child: widget) + : widget; + } + return widget; +} + +Widget _opacity(BuildContext context, Widget widget, Control control) { + var opacity = control.getDouble("opacity"); + var animation = control.getAnimation("animate_opacity"); + if (animation != null) { + return AnimatedOpacity( + duration: animation.duration, + curve: animation.curve, + opacity: opacity ?? 1.0, + onEnd: control.getBool("on_animation_end", false)! + ? () { + control.triggerEvent("animation_end", "opacity"); + } + : null, + child: widget, + ); + } else if (opacity != null) { + return Opacity( + opacity: opacity, + child: widget, + ); + } + return widget; +} + +Widget _rotatedControl(BuildContext context, Widget widget, Control control) { + var rotationDetails = control.getRotationDetails("rotate"); + var animation = control.getAnimation("animate_rotation"); + if (animation != null) { + return AnimatedRotation( + turns: rotationDetails != null ? rotationDetails.angle / (2 * pi) : 0, + alignment: rotationDetails?.alignment ?? Alignment.center, + duration: animation.duration, + curve: animation.curve, + onEnd: control.getBool("on_animation_end", false)! + ? () { + control.triggerEvent("animation_end", "rotation"); + } + : null, + child: widget, + ); + } else if (rotationDetails != null) { + return Transform.rotate( + angle: rotationDetails.angle, + alignment: rotationDetails.alignment, + child: widget, + ); + } + return widget; +} + +Widget _scaledControl(BuildContext context, Widget widget, Control control) { + var scaleDetails = control.getScale("scale"); + var animation = control.getAnimation("animate_scale"); + if (animation != null) { + return AnimatedScale( + scale: scaleDetails?.scale ?? 1.0, + alignment: scaleDetails?.alignment ?? Alignment.center, + duration: animation.duration, + curve: animation.curve, + onEnd: control.getBool("on_animation_end", false)! + ? () { + control.triggerEvent("animation_end", "scale"); + } + : null, + child: widget, + ); + } else if (scaleDetails != null) { + return Transform.scale( + scale: scaleDetails.scale, + scaleX: scaleDetails.scaleX, + scaleY: scaleDetails.scaleY, + alignment: scaleDetails.alignment, + child: widget, + ); + } + return widget; +} + +Widget _offsetControl(BuildContext context, Widget widget, Control control) { + var offset = control.getOffset("offset"); + var animation = control.getAnimation("animate_offset"); + if (offset != null && animation != null) { + return AnimatedSlide( + offset: offset, + duration: animation.duration, + curve: animation.curve, + onEnd: control.getBool("on_animation_end", false)! + ? () { + control.triggerEvent("animation_end", "offset"); + } + : null, + child: widget, + ); + } else if (offset != null) { + return FractionalTranslation(translation: offset, child: widget); + } + return widget; +} + +Widget _positionedControl( + BuildContext context, Widget widget, Control control) { + var left = control.getDouble("left", null); + var top = control.getDouble("top", null); + var right = control.getDouble("right", null); + var bottom = control.getDouble("bottom", null); + + var animation = control.getAnimation("animate_position"); + if (animation != null) { + if (left == null && top == null && right == null && bottom == null) { + left = 0; + top = 0; + } + + return AnimatedPositioned( + duration: animation.duration, + curve: animation.curve, + left: left, + top: top, + right: right, + bottom: bottom, + onEnd: control.getBool("on_animation_end", false)! + ? () { + control.triggerEvent("animation_end", "position"); + } + : null, + child: widget, + ); + } else if (left != null || top != null || right != null || bottom != null) { + var parent = control.parent; + if (!["Stack", "Page", "Overlay"].contains(parent?.type)) { + return ErrorControl("Error displaying ${control.type}", + description: + "Control can be positioned absolutely with \"left\", \"top\", \"right\" and \"bottom\" properties inside Stack control only."); + } + return Positioned( + left: left, + top: top, + right: right, + bottom: bottom, + child: widget, + ); + } + return widget; +} + +Widget _sizedControl(Widget widget, Control control) { + var width = control.getDouble("width"); + var height = control.getDouble("height"); + if ((width != null || height != null) && + !["container", "image"].contains(control.type)) { + widget = ConstrainedBox( + constraints: BoxConstraints.tightFor(width: width, height: height), + child: widget, + ); + } + var animation = control.getAnimation("animate_size"); + if (animation != null) { + return AnimatedSize( + duration: animation.duration, curve: animation.curve, child: widget); + } + return widget; +} diff --git a/packages/flet/lib/src/controls/bottom_app_bar.dart b/packages/flet/lib/src/controls/bottom_app_bar.dart index 7681ca269..2198107c6 100644 --- a/packages/flet/lib/src/controls/bottom_app_bar.dart +++ b/packages/flet/lib/src/controls/bottom_app_bar.dart @@ -1,68 +1,31 @@ +import 'package:flet/flet.dart'; import 'package:flutter/material.dart'; -import '../models/control.dart'; -import '../utils/edge_insets.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'flet_store_mixin.dart'; - -class BottomAppBarControl extends StatefulWidget { - final Control? parent; +class BottomAppBarControl extends StatelessWidget { final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - const BottomAppBarControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive}); + const BottomAppBarControl({super.key, required this.control}); @override - State createState() => _BottomAppBarControlState(); -} - -class _BottomAppBarControlState extends State - with FletStoreMixin { - @override Widget build(BuildContext context) { - debugPrint("BottomAppBarControl build: ${widget.control.id}"); - - bool disabled = widget.control.isDisabled || widget.parentDisabled; - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); - - var shape = parseNotchedShape(widget.control.attrString("shape")); - - var elevation = widget.control.attrDouble("elevation", 0)!; - - var clipBehavior = - parseClip(widget.control.attrString("clipBehavior"), Clip.none)!; - var bottomAppBar = withControls( - widget.children - .where((c) => c.isVisible && c.name == null) - .map((c) => c.id), (content, viewModel) { - return BottomAppBar( - clipBehavior: clipBehavior, - padding: parseEdgeInsets(widget.control, "padding"), - height: widget.control.attrDouble("height"), - elevation: elevation, - shape: shape, - shadowColor: widget.control.attrColor("shadowColor", context), - surfaceTintColor: widget.control.attrColor("surfaceTintColor", context), - color: widget.control.attrColor("bgColor", context), - notchMargin: widget.control.attrDouble("notchMargin", 4.0)!, - child: contentCtrls.isNotEmpty - ? createControl(widget.control, contentCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : null, - ); - }); - - return constrainedControl( - context, bottomAppBar, widget.parent, widget.control); + debugPrint("BottomAppBarControl build: ${control.id}"); + + var bottomAppBar = BottomAppBar( + clipBehavior: control.getClipBehavior("clip_behavior", Clip.none)!, + padding: control.getPadding("padding"), + height: control.getDouble("height"), + elevation: control.getDouble("elevation"), + shape: control.getNotchedShape("shape"), + shadowColor: control.getColor("shadow_color", context), + surfaceTintColor: control.getColor("surface_tint_color", context), + color: control.getColor("bgcolor", context), + notchMargin: control.getDouble("notch_margin", 4.0)!, + child: control.buildWidget("content"), + ); + + return ConstrainedControl( + control: control, + child: bottomAppBar, + ); } } diff --git a/packages/flet/lib/src/controls/bottom_sheet.dart b/packages/flet/lib/src/controls/bottom_sheet.dart index c8351ab28..ee7134f5b 100644 --- a/packages/flet/lib/src/controls/bottom_sheet.dart +++ b/packages/flet/lib/src/controls/bottom_sheet.dart @@ -1,32 +1,20 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/animations.dart'; import '../utils/borders.dart'; import '../utils/box.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/colors.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; class BottomSheetControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final Widget? nextChild; - final FletControlBackend backend; - const BottomSheetControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.nextChild, - required this.backend}); + BottomSheetControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _BottomSheetControlState(); @@ -37,51 +25,34 @@ class _BottomSheetControlState extends State { Widget build(BuildContext context) { debugPrint("BottomSheet build: ${widget.control.id}"); - bool lastOpen = widget.control.state["open"] ?? false; - bool disabled = widget.control.isDisabled || widget.parentDisabled; + bool lastOpen = widget.control.getBool("_open", false)!; + var open = widget.control.getBool("open", false)!; - var open = widget.control.attrBool("open", false)!; //var modal = widget.control.attrBool("modal", true)!; - var dismissible = widget.control.attrBool("dismissible", true)!; - var enableDrag = widget.control.attrBool("enableDrag", false)!; - var showDragHandle = widget.control.attrBool("showDragHandle", false)!; - var useSafeArea = widget.control.attrBool("useSafeArea", true)!; + var dismissible = widget.control.getBool("dismissible", true)!; + var enableDrag = widget.control.getBool("enable_drag", false)!; + var showDragHandle = widget.control.getBool("show_drag_handle", false)!; + var useSafeArea = widget.control.getBool("use_safe_area", true)!; var isScrollControlled = - widget.control.attrBool("isScrollControlled", false)!; + widget.control.getBool("is_scroll_controlled", false)!; var maintainBottomViewInsetsPadding = - widget.control.attrBool("maintainBottomViewInsetsPadding", true)!; - - void resetOpenState() { - widget.backend.updateControlState(widget.control.id, {"open": "false"}); - } + widget.control.getBool("maintain_bottom_view_insets_padding", true)!; if (open && !lastOpen) { - widget.control.state["open"] = open; + widget.control.updateProperties({"_open": open}, python: false); WidgetsBinding.instance.addPostFrameCallback((_) { showModalBottomSheet( context: context, builder: (context) { - var contentCtrls = widget.children - .where((c) => c.name == "content" && c.isVisible); + var content = widget.control.buildWidget("content"); - if (contentCtrls.isEmpty) { + if (content == null) { return const ErrorControl( - "BottomSheet.content must be provided and visible"); - } - - var content = createControl( - widget.control, contentCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive); - - if (content is ErrorControl) { - return content; + "BottomSheet.content must be visible"); } if (maintainBottomViewInsetsPadding) { - var bottomPadding = - MediaQuery.of(context).viewInsets.bottom; - debugPrint("bottomPadding: $bottomPadding"); content = Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom), @@ -92,30 +63,24 @@ class _BottomSheetControlState extends State { return content; }, isDismissible: dismissible, - backgroundColor: widget.control.attrColor("bgColor", context), - elevation: widget.control.attrDouble("elevation"), + backgroundColor: widget.control.getColor("bgcolor", context), + elevation: widget.control.getDouble("elevation"), isScrollControlled: isScrollControlled, enableDrag: enableDrag, - barrierColor: widget.control.attrColor("barrierColor", context), + barrierColor: widget.control.getColor("barrier_color", context), sheetAnimationStyle: - parseAnimationStyle(widget.control, "animationStyle"), + widget.control.getAnimationStyle("animation_style"), constraints: - parseBoxConstraints(widget.control, "sizeConstraints"), + widget.control.getBoxConstraints("size_constraints"), showDragHandle: showDragHandle, - clipBehavior: - parseClip(widget.control.attrString("clipBehavior")), - shape: parseOutlinedBorder(widget.control, "shape"), + clipBehavior: widget.control.getClipBehavior("clip_behavior"), + shape: widget.control + .getOutlinedBorder("shape", Theme.of(context)), useSafeArea: useSafeArea) .then((value) { - lastOpen = widget.control.state["open"] ?? false; - debugPrint("BottomSheet dismissed: $lastOpen"); - bool shouldDismiss = lastOpen; - widget.control.state["open"] = false; - - if (shouldDismiss) { - resetOpenState(); - widget.backend.triggerControlEvent(widget.control.id, "dismiss"); - } + widget.control.updateProperties({"_open": false}, python: false); + widget.control.updateProperties({"open": false}); + widget.control.triggerEvent("dismiss"); }); }); } else if (open != lastOpen && lastOpen) { diff --git a/packages/flet/lib/src/controls/button.dart b/packages/flet/lib/src/controls/button.dart new file mode 100644 index 000000000..1093ab52d --- /dev/null +++ b/packages/flet/lib/src/controls/button.dart @@ -0,0 +1,223 @@ +import 'package:flet/flet.dart'; +import 'package:flutter/material.dart'; + +class ButtonControl extends StatefulWidget { + final Control control; + + ButtonControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); + + @override + State createState() => _ButtonControlState(); +} + +class _ButtonControlState extends State with FletStoreMixin { + late final FocusNode _focusNode; + + @override + void initState() { + super.initState(); + _focusNode = FocusNode(); + _focusNode.addListener(_onFocusChange); + widget.control.addInvokeMethodListener(_invokeMethod); + } + + @override + void dispose() { + _focusNode.removeListener(_onFocusChange); + _focusNode.dispose(); + widget.control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } + + void _onFocusChange() { + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); + } + + Future _invokeMethod(String name, dynamic args) async { + debugPrint("Button.$name($args)"); + switch (name) { + case "focus": + _focusNode.requestFocus(); + default: + throw Exception("Unknown Button method: $name"); + } + } + + @override + Widget build(BuildContext context) { + debugPrint("Button build: ${widget.control.id}"); + + bool isFilledButton = widget.control.type == "FilledButton"; + bool isFilledTonalButton = widget.control.type == "FilledTonalButton"; + bool isTextButton = widget.control.type == "TextButton"; + bool isOutlinedButton = widget.control.type == "OutlinedButton"; + + var url = widget.control.getString("url"); + var iconColor = widget.control.getColor("icon_color", context); + var clipBehavior = + widget.control.getClipBehavior("clip_behavior", Clip.none)!; + var autofocus = widget.control.getBool("autofocus", false)!; + + var icon = widget.control.buildIconOrWidget("icon", color: iconColor); + var content = widget.control.buildTextOrWidget("content"); + + Function()? onPressed = !widget.control.disabled + ? () { + if (url != null) { + openWebBrowser(url, + webWindowName: widget.control.getString("url_target")); + } + widget.control.triggerEvent("click"); + } + : null; + + Function()? onLongPressHandler = !widget.control.disabled + ? () { + widget.control.triggerEvent("long_press"); + } + : null; + + Function(bool)? onHoverHandler = !widget.control.disabled + ? (state) { + widget.control.triggerEvent("hover", state); + } + : null; + + Widget? button; + + var theme = Theme.of(context); + + var style = widget.control.getButtonStyle("style", Theme.of(context), + defaultForegroundColor: widget.control + .getColor("color", context, theme.colorScheme.primary)!, + defaultBackgroundColor: widget.control + .getColor("bgcolor", context, theme.colorScheme.surface)!, + defaultOverlayColor: theme.colorScheme.primary.withOpacity(0.08), + defaultShadowColor: theme.colorScheme.shadow, + defaultSurfaceTintColor: theme.colorScheme.surfaceTint, + defaultElevation: widget.control.getDouble("elevation", 1)!, + defaultPadding: const EdgeInsets.symmetric(horizontal: 8), + defaultBorderSide: BorderSide.none, + defaultShape: theme.useMaterial3 + ? const StadiumBorder() + : RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))); + + Widget error = const ErrorControl("Error displaying Button", + description: "\"icon\" must be specified together with \"content\""); + + if (icon != null) { + if (isFilledButton) { + button = FilledButton.icon( + style: style, + autofocus: autofocus, + focusNode: _focusNode, + onPressed: onPressed, + onLongPress: onLongPressHandler, + onHover: onHoverHandler, + clipBehavior: clipBehavior, + icon: icon, + label: content ?? error); + } else if (isFilledTonalButton) { + button = FilledButton.tonalIcon( + style: style, + autofocus: autofocus, + focusNode: _focusNode, + onPressed: onPressed, + onLongPress: onLongPressHandler, + onHover: onHoverHandler, + clipBehavior: clipBehavior, + icon: icon, + label: content ?? error); + } else if (isTextButton) { + button = TextButton.icon( + autofocus: autofocus, + focusNode: _focusNode, + onPressed: onPressed, + onLongPress: onLongPressHandler, + onHover: onHoverHandler, + style: style, + clipBehavior: clipBehavior, + icon: icon, + label: content ?? error, + ); + } else if (isOutlinedButton) { + button = OutlinedButton.icon( + autofocus: autofocus, + focusNode: _focusNode, + onPressed: onPressed, + onLongPress: onLongPressHandler, + clipBehavior: clipBehavior, + style: style, + icon: icon, + label: content ?? error); + } else { + button = ElevatedButton.icon( + style: style, + autofocus: autofocus, + focusNode: _focusNode, + onPressed: onPressed, + onLongPress: onLongPressHandler, + onHover: onHoverHandler, + clipBehavior: clipBehavior, + icon: icon, + label: content ?? error); + } + } else { + if (isFilledButton) { + button = FilledButton( + style: style, + autofocus: autofocus, + focusNode: _focusNode, + onPressed: onPressed, + onLongPress: onLongPressHandler, + onHover: onHoverHandler, + clipBehavior: clipBehavior, + child: content); + } else if (isFilledTonalButton) { + button = FilledButton.tonal( + style: style, + autofocus: autofocus, + focusNode: _focusNode, + onPressed: onPressed, + onLongPress: onLongPressHandler, + onHover: onHoverHandler, + clipBehavior: clipBehavior, + child: content); + } else if (isTextButton) { + button = TextButton( + autofocus: autofocus, + focusNode: _focusNode, + style: style, + onPressed: onPressed, + onLongPress: onLongPressHandler, + onHover: onHoverHandler, + clipBehavior: clipBehavior, + child: + widget.control.buildTextOrWidget("content") ?? const Text("")); + } else if (isOutlinedButton) { + button = OutlinedButton( + autofocus: autofocus, + focusNode: _focusNode, + onPressed: onPressed, + onLongPress: onLongPressHandler, + clipBehavior: clipBehavior, + onHover: onHoverHandler, + style: style, + child: content); + } else { + button = ElevatedButton( + style: style, + autofocus: autofocus, + focusNode: _focusNode, + onPressed: onPressed, + onLongPress: onLongPressHandler, + onHover: onHoverHandler, + clipBehavior: clipBehavior, + child: content); + } + } + + return ConstrainedControl(control: widget.control, child: button); + } +} diff --git a/packages/flet/lib/src/controls/canvas.dart b/packages/flet/lib/src/controls/canvas.dart index 50b48e35b..5ce085f0b 100644 --- a/packages/flet/lib/src/controls/canvas.dart +++ b/packages/flet/lib/src/controls/canvas.dart @@ -1,71 +1,27 @@ -import 'dart:convert'; import 'dart:ui' as ui; -import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; +import 'package:flet/src/extensions/control.dart'; +import 'package:flet/src/utils/alignment.dart'; +import 'package:flet/src/utils/borders.dart'; +import 'package:flet/src/utils/colors.dart'; +import 'package:flet/src/utils/drawing.dart'; +import 'package:flet/src/utils/numbers.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_redux/flutter_redux.dart'; -import 'package:redux/redux.dart'; -import '../flet_control_backend.dart'; -import '../models/app_state.dart'; import '../models/control.dart'; -import '../models/control_tree_view_model.dart'; -import '../utils/alignment.dart'; -import '../utils/borders.dart'; import '../utils/dash_path.dart'; -import '../utils/drawing.dart'; import '../utils/images.dart'; -import '../utils/numbers.dart'; import '../utils/text.dart'; import '../utils/transforms.dart'; -import 'create_control.dart'; - -class CanvasViewModel extends Equatable { - final Control control; - final Control? child; - final List shapes; - - const CanvasViewModel( - {required this.control, required this.child, required this.shapes}); - - static CanvasViewModel fromStore( - Store store, Control control, List children) { - return CanvasViewModel( - control: control, - child: store.state.controls[control.id]!.childIds - .map((childId) => store.state.controls[childId]) - .nonNulls - .where((c) => c.name == "content" && c.isVisible) - .firstOrNull, - shapes: children - .where((c) => c.name != "content" && c.isVisible) - .map((c) => ControlTreeViewModel.fromStore(store, c)) - .toList()); - } - - @override - List get props => [control, shapes]; -} +import 'base_controls.dart'; typedef CanvasControlOnPaintCallback = void Function(Size size); class CanvasControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - - const CanvasControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + + CanvasControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _CanvasControlState(); @@ -77,58 +33,40 @@ class _CanvasControlState extends State { @override Widget build(BuildContext context) { - debugPrint("CustomPaint build: ${widget.control.id}"); - - var result = StoreConnector( - distinct: true, - ignoreChange: (state) { - return state.controls[widget.control.id] == null; + debugPrint("Canvas build: ${widget.control.id}"); + + var onResize = widget.control.getBool("on_resize", false)!; + var resizeInterval = widget.control.getInt("resize_interval", 10)!; + + var paint = CustomPaint( + painter: FletCustomPainter( + context: context, + theme: Theme.of(context), + shapes: widget.control.children("shapes"), + onPaintCallback: (size) { + if (onResize) { + var now = DateTime.now().millisecondsSinceEpoch; + if ((now - _lastResize > resizeInterval && _lastSize != size) || + _lastSize == null) { + _lastResize = now; + _lastSize = size; + widget.control + .triggerEvent("resize", {"w": size.width, "h": size.height}); + } + } }, - converter: (store) => - CanvasViewModel.fromStore(store, widget.control, widget.children), - builder: (context, viewModel) { - var onResize = viewModel.control.attrBool("onResize", false)!; - var resizeInterval = viewModel.control.attrInt("resizeInterval", 10)!; - - var paint = CustomPaint( - painter: FletCustomPainter( - context: context, - theme: Theme.of(context), - shapes: viewModel.shapes, - onPaintCallback: (size) { - if (onResize) { - var now = DateTime.now().millisecondsSinceEpoch; - if ((now - _lastResize > resizeInterval && - _lastSize != size) || - _lastSize == null) { - _lastResize = now; - _lastSize = size; - widget.backend.triggerControlEvent( - viewModel.control.id, - "resize", - json.encode({"w": size.width, "h": size.height})); - } - } - }, - ), - child: viewModel.child != null - ? createControl(viewModel.control, viewModel.child!.id, - viewModel.control.isDisabled, - parentAdaptive: widget.parentAdaptive) - : null, - ); - - return paint; - }); - - return constrainedControl(context, result, widget.parent, widget.control); + ), + child: widget.control.buildWidget("content"), + ); + + return ConstrainedControl(control: widget.control, child: paint); } } class FletCustomPainter extends CustomPainter { final BuildContext context; final ThemeData theme; - final List shapes; + final List shapes; final CanvasControlOnPaintCallback onPaintCallback; const FletCustomPainter( @@ -144,46 +82,45 @@ class FletCustomPainter extends CustomPainter { //debugPrint("SHAPE CONTROLS: $shapes"); for (var shape in shapes) { - if (shape.control.type == "line") { + shape.notifyParent = true; + if (shape.type == "Line") { drawLine(canvas, shape); - } else if (shape.control.type == "circle") { + } else if (shape.type == "Circle") { drawCircle(canvas, shape); - } else if (shape.control.type == "arc") { + } else if (shape.type == "Arc") { drawArc(canvas, shape); - } else if (shape.control.type == "color") { + } else if (shape.type == "Color") { drawColor(canvas, shape); - } else if (shape.control.type == "oval") { + } else if (shape.type == "Oval") { drawOval(canvas, shape); - } else if (shape.control.type == "fill") { + } else if (shape.type == "Fill") { drawFill(canvas, shape); - } else if (shape.control.type == "points") { + } else if (shape.type == "Points") { drawPoints(canvas, shape); - } else if (shape.control.type == "rect") { + } else if (shape.type == "Rect") { drawRect(canvas, shape); - } else if (shape.control.type == "path") { + } else if (shape.type == "Path") { drawPath(canvas, shape); - } else if (shape.control.type == "shadow") { + } else if (shape.type == "Shadow") { drawShadow(canvas, shape); - } else if (shape.control.type == "text") { + } else if (shape.type == "Text") { drawText(context, canvas, shape); } } } @override - bool shouldRepaint(FletCustomPainter oldDelegate) { + bool shouldRepaint(FletCustomPainter oldPainter) { return true; } - void drawLine(Canvas canvas, ControlTreeViewModel shape) { - Paint paint = parsePaint(theme, shape.control, "paint"); - var dashPattern = parsePaintStrokeDashPattern(shape.control, "paint"); + void drawLine(Canvas canvas, Control shape) { + Paint paint = shape.getPaint("paint", theme, Paint())!; + var dashPattern = shape.getPaintStrokeDashPattern("paint"); paint.style = ui.PaintingStyle.stroke; var path = ui.Path(); - path.moveTo( - shape.control.attrDouble("x1")!, shape.control.attrDouble("y1")!); - path.lineTo( - shape.control.attrDouble("x2")!, shape.control.attrDouble("y2")!); + path.moveTo(shape.getDouble("x1")!, shape.getDouble("y1")!); + path.lineTo(shape.getDouble("x2")!, shape.getDouble("y2")!); if (dashPattern != null) { path = dashPath(path, dashArray: CircularIntervalList(dashPattern)); @@ -191,73 +128,71 @@ class FletCustomPainter extends CustomPainter { canvas.drawPath(path, paint); } - void drawCircle(Canvas canvas, ControlTreeViewModel shape) { - var radius = shape.control.attrDouble("radius", 0)!; - Paint paint = parsePaint(theme, shape.control, "paint"); + void drawCircle(Canvas canvas, Control shape) { + var radius = shape.getDouble("radius", 0)!; + Paint paint = shape.getPaint("paint", theme, Paint())!; canvas.drawCircle( - Offset(shape.control.attrDouble("x")!, shape.control.attrDouble("y")!), - radius, - paint); + Offset(shape.getDouble("x")!, shape.getDouble("y")!), radius, paint); } - void drawOval(Canvas canvas, ControlTreeViewModel shape) { - var width = shape.control.attrDouble("width", 0)!; - var height = shape.control.attrDouble("height", 0)!; - Paint paint = parsePaint(theme, shape.control, "paint"); + void drawOval(Canvas canvas, Control shape) { + var width = shape.getDouble("width", 0)!; + var height = shape.getDouble("height", 0)!; + Paint paint = shape.getPaint("paint", theme, Paint())!; canvas.drawOval( - Rect.fromLTWH(shape.control.attrDouble("x")!, - shape.control.attrDouble("y")!, width, height), + Rect.fromLTWH( + shape.getDouble("x")!, shape.getDouble("y")!, width, height), paint); } - void drawArc(Canvas canvas, ControlTreeViewModel shape) { - var width = shape.control.attrDouble("width", 0)!; - var height = shape.control.attrDouble("height", 0)!; - var startAngle = shape.control.attrDouble("startAngle", 0)!; - var sweepAngle = shape.control.attrDouble("sweepAngle", 0)!; - var useCenter = shape.control.attrBool("useCenter", false)!; - Paint paint = parsePaint(theme, shape.control, "paint"); + void drawArc(Canvas canvas, Control shape) { + var width = shape.getDouble("width", 0)!; + var height = shape.getDouble("height", 0)!; + var startAngle = shape.getDouble("start_angle", 0)!; + var sweepAngle = shape.getDouble("sweep_angle", 0)!; + var useCenter = shape.getBool("use_center", false)!; + Paint paint = shape.getPaint("paint", theme, Paint())!; canvas.drawArc( - Rect.fromLTWH(shape.control.attrDouble("x")!, - shape.control.attrDouble("y")!, width, height), + Rect.fromLTWH( + shape.getDouble("x")!, shape.getDouble("y")!, width, height), startAngle, sweepAngle, useCenter, paint); } - void drawFill(Canvas canvas, ControlTreeViewModel shape) { - Paint paint = parsePaint(theme, shape.control, "paint"); + void drawFill(Canvas canvas, Control shape) { + Paint paint = shape.getPaint("paint", theme, Paint())!; canvas.drawPaint(paint); } - void drawColor(Canvas canvas, ControlTreeViewModel shape) { - var color = shape.control.attrColor("color", context) ?? Colors.black; - var blendMode = parseBlendMode( - shape.control.attrString("blendMode"), BlendMode.srcOver)!; + void drawColor(Canvas canvas, Control shape) { + var color = shape.getColor("color", context) ?? Colors.black; + var blendMode = + parseBlendMode(shape.getString("blend_mode"), BlendMode.srcOver)!; canvas.drawColor(color, blendMode); } - void drawPoints(Canvas canvas, ControlTreeViewModel shape) { - var points = parseOffsetList(shape.control, "points")!; + void drawPoints(Canvas canvas, Control shape) { + var points = parseOffsetList(shape.get("points"))!; var pointMode = ui.PointMode.values.firstWhere( (e) => e.name.toLowerCase() == - shape.control.attrString("pointMode", "")!.toLowerCase(), + shape.getString("point_mode", "")!.toLowerCase(), orElse: () => ui.PointMode.points); - Paint paint = parsePaint(theme, shape.control, "paint"); + Paint paint = shape.getPaint("paint", theme, Paint())!; canvas.drawPoints(pointMode, points, paint); } - void drawRect(Canvas canvas, ControlTreeViewModel shape) { - var width = shape.control.attrDouble("width", 0)!; - var height = shape.control.attrDouble("height", 0)!; - var borderRadius = parseBorderRadius(shape.control, "borderRadius"); - Paint paint = parsePaint(theme, shape.control, "paint"); + void drawRect(Canvas canvas, Control shape) { + var width = shape.getDouble("width", 0)!; + var height = shape.getDouble("height", 0)!; + var borderRadius = shape.getBorderRadius("border_radius"); + Paint paint = shape.getPaint("paint", theme, Paint())!; canvas.drawRRect( RRect.fromRectAndCorners( - Rect.fromLTWH(shape.control.attrDouble("x")!, - shape.control.attrDouble("y")!, width, height), + Rect.fromLTWH( + shape.getDouble("x")!, shape.getDouble("y")!, width, height), topLeft: borderRadius?.topLeft ?? Radius.zero, topRight: borderRadius?.topRight ?? Radius.zero, bottomLeft: borderRadius?.bottomLeft ?? Radius.zero, @@ -265,41 +200,34 @@ class FletCustomPainter extends CustomPainter { paint); } - void drawText( - BuildContext context, Canvas canvas, ControlTreeViewModel shape) { - var offset = - Offset(shape.control.attrDouble("x")!, shape.control.attrDouble("y")!); - var alignment = - parseAlignment(shape.control, "alignment", Alignment.topLeft)!; - var text = shape.control.attrString("text", "")!; - TextStyle style = parseTextStyle(theme, shape.control, "style") ?? - theme.textTheme.bodyMedium!; - + void drawText(BuildContext context, Canvas canvas, Control shape) { + var offset = Offset(shape.getDouble("x")!, shape.getDouble("y")!); + var alignment = shape.getAlignment("alignment", Alignment.topLeft)!; + var style = + shape.getTextStyle("style", theme, theme.textTheme.bodyMedium!)!; if (style.color == null) { style = style.copyWith(color: theme.textTheme.bodyMedium!.color); } - - TextAlign? textAlign = - parseTextAlign(shape.control.attrString("textAlign"), TextAlign.start)!; TextSpan span = TextSpan( - text: text, + text: shape.getString("text", "")!, style: style, - children: parseTextSpans(theme, shape, false, null)); + children: parseTextSpans(shape.children("spans"), theme)); - var maxLines = shape.control.attrInt("maxLines"); - var maxWidth = shape.control.attrDouble("maxWidth"); - var ellipsis = shape.control.attrString("ellipsis"); + var maxLines = shape.getInt("max_lines"); + var maxWidth = shape.getDouble("max_width"); + var ellipsis = shape.getString("ellipsis"); // paint TextPainter textPainter = TextPainter( text: span, - textAlign: textAlign, + textAlign: + parseTextAlign(shape.getString("text_align"), TextAlign.start)!, maxLines: maxLines, ellipsis: ellipsis, textDirection: Directionality.of(context)); textPainter.layout(maxWidth: maxWidth ?? double.infinity); - var angle = shape.control.attrDouble("rotate", 0)!; + var angle = shape.getDouble("rotate", 0)!; final delta = Offset( offset.dx - textPainter.size.width / 2 * (alignment.x + 1.0), @@ -314,23 +242,21 @@ class FletCustomPainter extends CustomPainter { canvas.restore(); } - void drawPath(Canvas canvas, ControlTreeViewModel shape) { - var path = - buildPath(json.decode(shape.control.attrString("elements", "[]")!)); - Paint paint = parsePaint(theme, shape.control, "paint"); - var dashPattern = parsePaintStrokeDashPattern(shape.control, "paint"); + void drawPath(Canvas canvas, Control shape) { + var path = buildPath(shape.get("elements", [])!); + Paint paint = shape.getPaint("paint", theme, Paint())!; + var dashPattern = shape.getPaintStrokeDashPattern("paint"); if (dashPattern != null) { path = dashPath(path, dashArray: CircularIntervalList(dashPattern)); } canvas.drawPath(path, paint); } - void drawShadow(Canvas canvas, ControlTreeViewModel shape) { - var path = buildPath(json.decode(shape.control.attrString("path", "[]")!)); - var color = shape.control.attrColor("color", context) ?? Colors.black; - var elevation = shape.control.attrDouble("elevation", 0)!; - var transparentOccluder = - shape.control.attrBool("transparentOccluder", false)!; + void drawShadow(Canvas canvas, Control shape) { + var path = buildPath(shape.get("path", [])!); + var color = shape.getColor("color", context) ?? Colors.black; + var elevation = shape.getDouble("elevation", 0)!; + var transparentOccluder = shape.getBool("transparent_occluder", false)!; canvas.drawShadow(path, color, elevation, transparentOccluder); } @@ -340,7 +266,7 @@ class FletCustomPainter extends CustomPainter { return path; } for (var elem in (j as List)) { - var type = elem["type"]; + var type = elem["_type"]; if (type == "moveto") { path.moveTo(parseDouble(elem["x"], 0)!, parseDouble(elem["y"], 0)!); } else if (type == "lineto") { @@ -368,7 +294,7 @@ class FletCustomPainter extends CustomPainter { parseDouble(elem["width"], 0)!, parseDouble(elem["height"], 0)!)); } else if (type == "rect") { - var borderRadius = borderRadiusFromJSON(elem["border_radius"]); + var borderRadius = parseBorderRadius(elem["border_radius"]); path.addRRect(RRect.fromRectAndCorners( Rect.fromLTWH( parseDouble(elem["x"], 0)!, diff --git a/packages/flet/lib/src/controls/card.dart b/packages/flet/lib/src/controls/card.dart index 775acd972..bce88416c 100644 --- a/packages/flet/lib/src/controls/card.dart +++ b/packages/flet/lib/src/controls/card.dart @@ -2,52 +2,46 @@ import 'package:flutter/material.dart'; import '../models/control.dart'; import '../utils/borders.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; +import 'control_widget.dart'; class CardControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - const CardControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive}); + const CardControl({ + super.key, + required this.control, + }); @override Widget build(BuildContext context) { debugPrint("Card build: ${control.id}"); - bool disabled = control.isDisabled || parentDisabled; - bool? adaptive = control.attrBool("adaptive") ?? parentAdaptive; - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - var content = contentCtrls.isNotEmpty - ? createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: adaptive) + var contentCtrl = control.child("content"); + var contentWidget = contentCtrl != null + ? ControlWidget( + control: contentCtrl, + ) : null; - var clipBehavior = parseClip(control.attrString("clipBehavior")); - var elevation = control.attrDouble("elevation"); - var shape = parseOutlinedBorder(control, "shape"); - var margin = parseEdgeInsets(control, "margin"); - var isSemanticContainer = control.attrBool("isSemanticContainer", true)!; + var clipBehavior = control.getClipBehavior("clip_behavior"); + var elevation = control.getDouble("elevation"); + var shape = control.getShape("shape", Theme.of(context)); + var margin = control.getMargin("margin"); + var isSemanticContainer = control.getBool("is_semantic_container", true)!; var showBorderOnForeground = - control.attrBool("showBorderOnForeground", true)!; - var color = control.attrColor("color", context); - var shadowColor = control.attrColor("shadowColor", context); - var surfaceTintColor = control.attrColor("surfaceTintColor", context); + control.getBool("show_border_on_foreground", true)!; + var color = control.getColor("color", context); + var shadowColor = control.getColor("shadow_color", context); + var surfaceTintColor = control.getColor("surface_tint_color", context); Widget? card; CardVariant variant = - parseCardVariant(control.attrString("variant"), CardVariant.elevated)!; + control.getCardVariant("variant", CardVariant.elevated)!; if (variant == CardVariant.outlined) { card = Card.outlined( @@ -60,7 +54,7 @@ class CardControl extends StatelessWidget { color: color, shadowColor: shadowColor, surfaceTintColor: surfaceTintColor, - child: content); + child: contentWidget); } else if (variant == CardVariant.filled) { card = Card.filled( elevation: elevation, @@ -72,7 +66,7 @@ class CardControl extends StatelessWidget { color: color, shadowColor: shadowColor, surfaceTintColor: surfaceTintColor, - child: content); + child: contentWidget); } else { card = Card( elevation: elevation, @@ -84,9 +78,9 @@ class CardControl extends StatelessWidget { color: color, shadowColor: shadowColor, surfaceTintColor: surfaceTintColor, - child: content); + child: contentWidget); } - return constrainedControl(context, card, parent, control); + return ConstrainedControl(control: control, child: card); } } diff --git a/packages/flet/lib/src/controls/charts.dart b/packages/flet/lib/src/controls/charts.dart deleted file mode 100644 index d0cf39149..000000000 --- a/packages/flet/lib/src/controls/charts.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; -import 'package:redux/redux.dart'; - -import '../models/app_state.dart'; -import '../models/control.dart'; - -class ChartAxisLabelViewModel extends Equatable { - final double value; - final Control? control; - - const ChartAxisLabelViewModel({required this.value, required this.control}); - - static ChartAxisLabelViewModel fromStore( - Store store, Control control) { - return ChartAxisLabelViewModel( - value: control.attrDouble("value")!, - control: store.state.controls[control.id]!.childIds - .map((childId) => store.state.controls[childId]) - .nonNulls - .where((c) => c.isVisible) - .firstOrNull); - } - - @override - List get props => [value, control]; -} - -class ChartAxisViewModel extends Equatable { - final Control control; - final Control? title; - final Map labels; - - const ChartAxisViewModel( - {required this.control, required this.title, required this.labels}); - - static ChartAxisViewModel fromStore(Store store, Control control) { - var children = store.state.controls[control.id]!.childIds - .map((childId) => store.state.controls[childId]) - .nonNulls - .where((c) => c.isVisible); - - return ChartAxisViewModel( - control: control, - title: children.where((c) => c.name == "t" && c.isVisible).firstOrNull, - labels: { - for (var e in children - .where((c) => c.name == "l" && c.isVisible) - .map((c) => ChartAxisLabelViewModel.fromStore(store, c)) - .where((c) => c.control != null)) - e.value: e.control! - }); - } - - @override - List get props => [control, title, labels]; -} diff --git a/packages/flet/lib/src/controls/checkbox.dart b/packages/flet/lib/src/controls/checkbox.dart index 2fb6b55a6..2917336f5 100644 --- a/packages/flet/lib/src/controls/checkbox.dart +++ b/packages/flet/lib/src/controls/checkbox.dart @@ -1,41 +1,28 @@ -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/borders.dart'; import '../utils/colors.dart'; +import '../utils/misc.dart'; import '../utils/mouse.dart'; -import '../utils/others.dart'; +import '../utils/numbers.dart'; import '../utils/text.dart'; import '../utils/theme.dart'; -import 'create_control.dart'; -import 'cupertino_checkbox.dart'; -import 'flet_store_mixin.dart'; +import 'base_controls.dart'; import 'list_tile.dart'; class CheckboxControl extends StatefulWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - final List children; - - const CheckboxControl( - {super.key, - this.parent, - required this.control, - required this.parentDisabled, - required this.parentAdaptive, - required this.children, - required this.backend}); + + CheckboxControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _CheckboxControlState(); } -class _CheckboxControlState extends State with FletStoreMixin { +class _CheckboxControlState extends State { bool? _value; bool _tristate = false; late final FocusNode _focusNode; @@ -48,8 +35,7 @@ class _CheckboxControlState extends State with FletStoreMixin { } void _onFocusChange() { - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); } @override @@ -72,114 +58,84 @@ class _CheckboxControlState extends State with FletStoreMixin { } void _onChange(bool? value) { - var svalue = value != null ? value.toString() : ""; _value = value; - widget.backend.updateControlState(widget.control.id, {"value": svalue}); - widget.backend.triggerControlEvent(widget.control.id, "change", svalue); + widget.control.updateProperties({"value": value}, notify: true); + widget.control.triggerEvent("change", value); } @override Widget build(BuildContext context) { debugPrint("Checkbox build: ${widget.control.id}"); - return withPagePlatform((context, platform) { - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - bool disabled = widget.control.isDisabled || widget.parentDisabled; - double? width = widget.control.attrDouble("width"); - double? height = widget.control.attrDouble("height"); - - if (adaptive == true && - (platform == TargetPlatform.iOS || - platform == TargetPlatform.macOS)) { - return CupertinoCheckboxControl( - control: widget.control, - parentDisabled: widget.parentDisabled, - backend: widget.backend); - } - var label = widget.children.firstWhereOrNull((c) => c.isVisible); - String labelStr = widget.control.attrString("label", "")!; - LabelPosition labelPosition = parseLabelPosition( - widget.control.attrString("labelPosition"), LabelPosition.right)!; - _tristate = widget.control.attrBool("tristate", false)!; - bool autofocus = widget.control.attrBool("autofocus", false)!; - bool? value = widget.control.attrBool("value", _tristate ? null : false); - if (_value != value) { - _value = value; - } - - TextStyle? labelStyle = - parseTextStyle(Theme.of(context), widget.control, "labelStyle"); - if (disabled && labelStyle != null) { - labelStyle = labelStyle.apply(color: Theme.of(context).disabledColor); - } - - var checkbox = Checkbox( - autofocus: autofocus, - focusNode: _focusNode, - value: _value, - isError: widget.control.attrBool("isError", false)!, - semanticLabel: widget.control.attrString("semanticsLabel"), - shape: parseOutlinedBorder(widget.control, "shape"), - side: parseWidgetStateBorderSide( - Theme.of(context), widget.control, "borderSide"), - splashRadius: widget.control.attrDouble("splashRadius"), - activeColor: widget.control.attrColor("activeColor", context), - focusColor: widget.control.attrColor("focusColor", context), - hoverColor: widget.control.attrColor("hoverColor", context), - overlayColor: parseWidgetStateColor( - Theme.of(context), widget.control, "overlayColor"), - checkColor: widget.control.attrColor("checkColor", context), - fillColor: parseWidgetStateColor( - Theme.of(context), widget.control, "fillColor"), - tristate: _tristate, - visualDensity: - parseVisualDensity(widget.control.attrString("visualDensity")), - mouseCursor: - parseMouseCursor(widget.control.attrString("mouseCursor")), - onChanged: !disabled - ? (bool? value) { - _onChange(value); - } - : null); - - ListTileClicks.of(context)?.notifier.addListener(() { - _toggleValue(); - }); - - Widget result = checkbox; - if (label != null || (labelStr != "")) { - Widget? labelWidget; - if (label != null) { - labelWidget = - createControl(widget.control, label.id, disabled); - } else { - labelWidget = disabled - ? Text(labelStr, style: labelStyle) - : MouseRegion( - cursor: SystemMouseCursors.click, - child: Text(labelStr, style: labelStyle)); - } - - result = MergeSemantics( - child: GestureDetector( - onTap: !disabled ? _toggleValue : null, - child: labelPosition == LabelPosition.right - ? Row(children: [checkbox, labelWidget]) - : Row(children: [labelWidget, checkbox]))); - } - if (width != null || height != null) { - result = SizedBox( + _tristate = widget.control.getBool("tristate", false)!; + var value = widget.control.getBool("value", _tristate ? null : false); + if (_value != value) { + _value = value; + } + + var checkbox = Checkbox( + autofocus: widget.control.getBool("autofocus", false)!, + focusNode: _focusNode, + value: _value, + isError: widget.control.getBool("is_error", false)!, + semanticLabel: widget.control.getString("semantics_label"), + shape: widget.control.getShape("shape", Theme.of(context)), + side: widget.control + .getWidgetStateBorderSide("border_side", Theme.of(context)), + splashRadius: widget.control.getDouble("splash_radius"), + activeColor: widget.control.getColor("active_color", context), + focusColor: widget.control.getColor("focus_color", context), + hoverColor: widget.control.getColor("hover_color", context), + overlayColor: widget.control + .getWidgetStateColor("overlay_color", Theme.of(context)), + checkColor: widget.control.getColor("check_color", context), + fillColor: + widget.control.getWidgetStateColor("fill_color", Theme.of(context)), + tristate: _tristate, + visualDensity: widget.control.getVisualDensity("visual_density"), + mouseCursor: widget.control.getMouseCursor("mouse_cursor"), + onChanged: !widget.control.disabled + ? (bool? value) => _onChange(value) + : null); + + // Add listener to ListTile clicks + ListTileClicks.of(context)?.notifier.addListener(() { + _toggleValue(); + }); + + Widget result = checkbox; + + var labelStyle = + widget.control.getTextStyle("label_style", Theme.of(context)); + if (widget.control.disabled && labelStyle != null) { + labelStyle = labelStyle.apply(color: Theme.of(context).disabledColor); + } + var label = + widget.control.buildTextOrWidget("label", textStyle: labelStyle); + if (label != null) { + label = widget.control.disabled + ? label + : MouseRegion(cursor: SystemMouseCursors.click, child: label); + var labelPosition = widget.control + .getLabelPosition("label_position", LabelPosition.right)!; + result = MergeSemantics( + child: GestureDetector( + onTap: !widget.control.disabled ? _toggleValue : null, + child: labelPosition == LabelPosition.right + ? Row(children: [checkbox, label]) + : Row(children: [label, checkbox]))); + } + + // Apply width and height if provided + var width = widget.control.getDouble("width"); + var height = widget.control.getDouble("height"); + if (width != null || height != null) { + result = SizedBox( width: width, height: height, - child: FittedBox( - fit: BoxFit.fill, - child: result, - ), - ); - } - - return constrainedControl(context, result, widget.parent, widget.control); - }); + child: FittedBox(fit: BoxFit.fill, child: result)); + } + + return ConstrainedControl(control: widget.control, child: result); } } diff --git a/packages/flet/lib/src/controls/chip.dart b/packages/flet/lib/src/controls/chip.dart index 7fe0b7cbd..255b70bd0 100644 --- a/packages/flet/lib/src/controls/chip.dart +++ b/packages/flet/lib/src/controls/chip.dart @@ -1,34 +1,24 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/animations.dart'; import '../utils/borders.dart'; import '../utils/box.dart'; import '../utils/colors.dart'; import '../utils/edge_insets.dart'; -import '../utils/others.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; import '../utils/text.dart'; import '../utils/theme.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; class ChipControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const ChipControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + ChipControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _ChipControlState(); @@ -36,7 +26,6 @@ class ChipControl extends StatefulWidget { class _ChipControlState extends State { bool _selected = false; - late final FocusNode _focusNode; @override @@ -53,129 +42,98 @@ class _ChipControlState extends State { super.dispose(); } - void _onSelect(bool selected) { - var strSelected = selected.toString(); - debugPrint(strSelected); - _selected = selected; - widget.backend - .updateControlState(widget.control.id, {"selected": strSelected}); - widget.backend - .triggerControlEvent(widget.control.id, "select", strSelected); - } - void _onFocusChange() { - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); } @override Widget build(BuildContext context) { debugPrint("Chip build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; + bool disabled = widget.control.disabled; - var labelCtrls = - widget.children.where((c) => c.name == "label" && c.isVisible); - if (labelCtrls.isEmpty) { + var label = widget.control.buildTextOrWidget("label"); + if (label == null) { return const ErrorControl("Chip.label must be provided and visible"); } - var leadingCtrls = - widget.children.where((c) => c.name == "leading" && c.isVisible); - var deleteIconCtrls = - widget.children.where((c) => c.name == "deleteIcon" && c.isVisible); - - var onClick = widget.control.attrBool("onclick", false)!; - var onDelete = widget.control.attrBool("onDelete", false)!; - var onSelect = widget.control.attrBool("onSelect", false)!; + var onClick = widget.control.getBool("on_click", false)!; + var onDelete = widget.control.getBool("on_delete", false)!; + var onSelect = widget.control.getBool("on_select", false)!; if (onSelect && onClick) { return const ErrorControl( "Chip cannot have both on_select and on_click events specified"); } - bool selected = widget.control.attrBool("selected", false)!; + bool selected = widget.control.getBool("selected", false)!; if (_selected != selected) { _selected = selected; } - return constrainedControl( - context, - InputChip( - autofocus: widget.control.attrBool("autofocus", false)!, - focusNode: _focusNode, - label: createControl(widget.control, labelCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive), - avatar: leadingCtrls.isNotEmpty - ? createControl(widget.control, leadingCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : null, - backgroundColor: widget.control.attrColor("bgcolor", context), - checkmarkColor: widget.control.attrColor("checkColor", context), - selected: _selected, - showCheckmark: widget.control.attrBool("showCheckmark", true)!, - deleteButtonTooltipMessage: - widget.control.attrString("deleteButtonTooltip"), - deleteIcon: deleteIconCtrls.isNotEmpty - ? createControl( - widget.control, deleteIconCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : null, - deleteIconColor: widget.control.attrColor("deleteIconColor", context), - disabledColor: widget.control.attrColor("disabledColor", context), - elevation: widget.control.attrDouble("elevation"), - isEnabled: !disabled, - padding: parseEdgeInsets(widget.control, "padding"), - labelPadding: parseEdgeInsets(widget.control, "labelPadding"), - labelStyle: - parseTextStyle(Theme.of(context), widget.control, "labelStyle"), - selectedColor: widget.control.attrColor("selectedColor", context), - selectedShadowColor: - widget.control.attrColor("selectedShadowColor", context), - shadowColor: widget.control.attrColor("shadowColor", context), - shape: parseOutlinedBorder(widget.control, "shape"), - color: - parseWidgetStateColor(Theme.of(context), widget.control, "color"), - surfaceTintColor: - widget.control.attrColor("surfaceTintColor", context), - pressElevation: widget.control.attrDouble("clickElevation"), - side: - parseBorderSide(Theme.of(context), widget.control, "borderSide"), - clipBehavior: - parseClip(widget.control.attrString("clipBehavior"), Clip.none)!, - visualDensity: - parseVisualDensity(widget.control.attrString("visualDensity")), - avatarBoxConstraints: - parseBoxConstraints(widget.control, "leadingSizeConstraints"), - deleteIconBoxConstraints: - parseBoxConstraints(widget.control, "deleteIconSizeConstraints"), - chipAnimationStyle: ChipAnimationStyle( - enableAnimation: - parseAnimationStyle(widget.control, "enableAnimationStyle"), - selectAnimation: - parseAnimationStyle(widget.control, "selectAnimationStyle"), - avatarDrawerAnimation: parseAnimationStyle( - widget.control, "leadingDrawerAnimationStyle"), - deleteDrawerAnimation: parseAnimationStyle( - widget.control, "deleteDrawerAnimationStyle"), - ), - onPressed: onClick && !disabled - ? () { - widget.backend - .triggerControlEvent(widget.control.id, "click"); - } - : null, - onDeleted: onDelete && !disabled - ? () { - widget.backend - .triggerControlEvent(widget.control.id, "delete"); - } - : null, - onSelected: onSelect && !disabled - ? (bool selected) { - _onSelect(selected); - } - : null, - ), - widget.parent, - widget.control); + final chip = InputChip( + autofocus: widget.control.getBool("autofocus", false)!, + focusNode: _focusNode, + label: label, + avatar: widget.control.buildWidget("leading"), + backgroundColor: widget.control.getColor("bgcolor", context), + checkmarkColor: widget.control.getColor("check_color", context), + selected: _selected, + showCheckmark: widget.control.getBool("show_checkmark", true)!, + deleteButtonTooltipMessage: + widget.control.getString("delete_button_tooltip"), + deleteIcon: widget.control.buildWidget("delete_icon"), + deleteIconColor: widget.control.getColor("delete_icon_color", context), + disabledColor: widget.control.getColor("disabled_color", context), + elevation: widget.control.getDouble("elevation"), + isEnabled: !disabled, + padding: widget.control.getPadding("padding"), + labelPadding: widget.control.getPadding("label_padding"), + labelStyle: widget.control.getTextStyle("label_style", Theme.of(context)), + selectedColor: widget.control.getColor("selected_color", context), + selectedShadowColor: + widget.control.getColor("selected_shadow_color", context), + shadowColor: widget.control.getColor("shadow_color", context), + shape: widget.control.getShape("shape", Theme.of(context)), + color: widget.control.getWidgetStateColor("color", Theme.of(context)), + surfaceTintColor: widget.control.getColor("surface_tint_color", context), + pressElevation: widget.control.getDouble("click_elevation"), + side: widget.control.getBorderSide("border_side", Theme.of(context)), + clipBehavior: + parseClip(widget.control.getString("clip_behavior"), Clip.none)!, + visualDensity: widget.control.getVisualDensity("visual_density"), + avatarBoxConstraints: + widget.control.getBoxConstraints("leading_size_constraints"), + deleteIconBoxConstraints: parseBoxConstraints( + widget.control.get("delete_icon_size_constraints")), + chipAnimationStyle: ChipAnimationStyle( + enableAnimation: + widget.control.getAnimationStyle("enable_animation_style"), + selectAnimation: + widget.control.getAnimationStyle("select_animation_style"), + avatarDrawerAnimation: parseAnimationStyle( + widget.control.get("leading_drawer_animation_style")), + deleteDrawerAnimation: parseAnimationStyle( + widget.control.get("delete_drawer_animation_style")), + ), + onPressed: onClick && !disabled + ? () { + widget.control.triggerEvent("click"); + } + : null, + onDeleted: onDelete && !disabled + ? () { + widget.control.triggerEvent("delete"); + } + : null, + onSelected: onSelect && !disabled + ? (bool selected) { + _selected = selected; + widget.control + .updateProperties({"selected": selected}, notify: true); + widget.control.triggerEvent("select", selected); + } + : null, + ); + + return ConstrainedControl(control: widget.control, child: chip); } } diff --git a/packages/flet/lib/src/controls/circle_avatar.dart b/packages/flet/lib/src/controls/circle_avatar.dart index 8dd1e25ae..d99e63c95 100644 --- a/packages/flet/lib/src/controls/circle_avatar.dart +++ b/packages/flet/lib/src/controls/circle_avatar.dart @@ -1,92 +1,77 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import '../utils/images.dart'; -import 'create_control.dart'; -import 'flet_store_mixin.dart'; +import '../utils/colors.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; -class CircleAvatarControl extends StatelessWidget with FletStoreMixin { - final Control? parent; +class CircleAvatarControl extends StatelessWidget { final Control control; - final List children; - final bool parentDisabled; - final FletControlBackend backend; - const CircleAvatarControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.backend}); + const CircleAvatarControl({ + super.key, + required this.control, + }); @override Widget build(BuildContext context) { debugPrint("CircleAvatar build: ${control.id}"); - bool disabled = control.isDisabled || parentDisabled; - return withPageArgs((context, pageArgs) { - var foregroundImageSrc = control.attrString("foregroundImageSrc"); - var backgroundImageSrc = control.attrString("backgroundImageSrc"); - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); + var foregroundImageSrc = control.getString("foreground_image_src"); + var backgroundImageSrc = control.getString("background_image_src"); + var content = control.buildTextOrWidget("content"); - ImageProvider? backgroundImage; - ImageProvider? foregroundImage; + ImageProvider? backgroundImage; + ImageProvider? foregroundImage; - if (foregroundImageSrc != null || backgroundImageSrc != null) { - var assetSrc = getAssetSrc((foregroundImageSrc ?? backgroundImageSrc)!, - pageArgs.pageUri!, pageArgs.assetsDir); + if (foregroundImageSrc != null || backgroundImageSrc != null) { + var assetSrc = control.backend + .getAssetSource((foregroundImageSrc ?? backgroundImageSrc)!); - // foregroundImage - if (foregroundImageSrc != null) { - if (assetSrc.isFile) { - // from File - foregroundImage = AssetImage(assetSrc.path); - } else { - // URL - foregroundImage = NetworkImage(assetSrc.path); - } + // foregroundImage + if (foregroundImageSrc != null) { + if (assetSrc.isFile) { + // from File + foregroundImage = AssetImage(assetSrc.path); + } else { + // URL + foregroundImage = NetworkImage(assetSrc.path); } + } - // backgroundImage - if (backgroundImageSrc != null) { - if (assetSrc.isFile) { - // from File - backgroundImage = AssetImage(assetSrc.path); - } else { - // URL - backgroundImage = NetworkImage(assetSrc.path); - } + // backgroundImage + if (backgroundImageSrc != null) { + if (assetSrc.isFile) { + // from File + backgroundImage = AssetImage(assetSrc.path); + } else { + // URL + backgroundImage = NetworkImage(assetSrc.path); } } + } - var avatar = CircleAvatar( - foregroundImage: foregroundImage, - backgroundImage: backgroundImage, - backgroundColor: control.attrColor("bgColor", context), - foregroundColor: control.attrColor("color", context), - radius: control.attrDouble("radius"), - minRadius: control.attrDouble("minRadius"), - maxRadius: control.attrDouble("maxRadius"), - onBackgroundImageError: backgroundImage != null - ? (object, trace) { - backend.triggerControlEvent( - control.id, "imageError", "background"); - } - : null, - onForegroundImageError: foregroundImage != null - ? (object, trace) { - backend.triggerControlEvent( - control.id, "imageError", "foreground"); - } - : null, - child: contentCtrls.isNotEmpty - ? createControl(control, contentCtrls.first.id, disabled) - : null); + var avatar = CircleAvatar( + foregroundImage: foregroundImage, + backgroundImage: backgroundImage, + backgroundColor: control.getColor("bgcolor", context), + foregroundColor: control.getColor("color", context), + radius: control.getDouble("radius"), + minRadius: control.getDouble("min_radius"), + maxRadius: control.getDouble("max_radius"), + onBackgroundImageError: backgroundImage != null + ? (object, trace) { + control.triggerEvent("image_error", "background"); + } + : null, + onForegroundImageError: foregroundImage != null + ? (object, trace) { + control.triggerEvent("image_error", "foreground"); + } + : null, + child: content); - return constrainedControl(context, avatar, parent, control); - }); + return ConstrainedControl(control: control, child: avatar); } } diff --git a/packages/flet/lib/src/controls/column.dart b/packages/flet/lib/src/controls/column.dart index ae7ca106f..2ba1a4dee 100644 --- a/packages/flet/lib/src/controls/column.dart +++ b/packages/flet/lib/src/controls/column.dart @@ -1,53 +1,40 @@ import 'package:flutter/widgets.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/alignment.dart'; -import 'create_control.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; import 'scroll_notification_control.dart'; import 'scrollable_control.dart'; class ColumnControl extends StatelessWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final bool? parentAdaptive; - final List children; - final FletControlBackend backend; - const ColumnControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const ColumnControl({ + super.key, + required this.control, + }); @override Widget build(BuildContext context) { debugPrint("Column build: ${control.id}"); - bool disabled = control.isDisabled || parentDisabled; - bool? adaptive = control.attrBool("adaptive") ?? parentAdaptive; - var spacing = control.attrDouble("spacing", 10)!; - var alignment = control.attrString("alignment"); - var tight = control.attrBool("tight", false)!; - var wrap = control.attrBool("wrap", false)!; - var horizontalAlignment = control.attrString("horizontalAlignment"); - - List controls = children.where((c) => c.isVisible).map((c) { - return createControl(control, c.id, disabled, parentAdaptive: adaptive); - }).toList(); + var spacing = control.getDouble("spacing", 10)!; + var alignment = control.getString("alignment"); + var tight = control.getBool("tight", false)!; + var wrap = control.getBool("wrap", false)!; + var horizontalAlignment = control.getString("horizontal_alignment"); + var controls = control.buildWidgets("controls"); Widget child = wrap ? Wrap( direction: Axis.vertical, spacing: spacing, - runSpacing: control.attrDouble("runSpacing", 10)!, + runSpacing: control.getDouble("run_spacing", 10)!, alignment: parseWrapAlignment(alignment, WrapAlignment.start)!, runAlignment: parseWrapAlignment( - control.attrString("runAlignment"), WrapAlignment.start)!, + control.getString("run_alignment"), WrapAlignment.start)!, crossAxisAlignment: parseWrapCrossAlignment( horizontalAlignment, WrapCrossAlignment.start)!, children: controls, @@ -65,16 +52,13 @@ class ColumnControl extends StatelessWidget { child = ScrollableControl( control: control, scrollDirection: wrap ? Axis.horizontal : Axis.vertical, - backend: backend, - parentAdaptive: adaptive, child: child, ); - if (control.attrBool("onScroll", false)!) { - child = ScrollNotificationControl( - control: control, backend: backend, child: child); + if (control.getBool("on_scroll", false)!) { + child = ScrollNotificationControl(control: control, child: child); } - return constrainedControl(context, child, parent, control); + return ConstrainedControl(control: control, child: child); } } diff --git a/packages/flet/lib/src/controls/container.dart b/packages/flet/lib/src/controls/container.dart index ffde07f33..80b2101cb 100644 --- a/packages/flet/lib/src/controls/container.dart +++ b/packages/flet/lib/src/controls/container.dart @@ -1,294 +1,207 @@ -import 'dart:convert'; - +import 'package:flet/flet.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; -import '../models/control.dart'; -import '../utils/alignment.dart'; -import '../utils/animations.dart'; -import '../utils/borders.dart'; -import '../utils/box.dart'; -import '../utils/edge_insets.dart'; -import '../utils/gradient.dart'; -import '../utils/images.dart'; -import '../utils/launch_url.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'flet_store_mixin.dart'; - -class ContainerTapEvent { - final double localX; - final double localY; - final double globalX; - final double globalY; - - ContainerTapEvent( - {required this.localX, - required this.localY, - required this.globalX, - required this.globalY}); - - Map toJson() => { - 'lx': localX, - 'ly': localY, - 'gx': globalX, - 'gy': globalY - }; -} - class ContainerControl extends StatelessWidget with FletStoreMixin { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const ContainerControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const ContainerControl({ + super.key, + required this.control, + }); @override Widget build(BuildContext context) { debugPrint("Container build: ${control.id}"); - var bgColor = control.attrColor("bgColor", context); - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - bool ink = control.attrBool("ink", false)!; - bool onClick = control.attrBool("onclick", false)!; - bool onTapDown = control.attrBool("onTapDown", false)!; - String url = control.attrString("url", "")!; - String? urlTarget = control.attrString("urlTarget"); - bool onLongPress = control.attrBool("onLongPress", false)!; - bool onHover = control.attrBool("onHover", false)!; - bool ignoreInteractions = control.attrBool("ignoreInteractions", false)!; - bool disabled = control.isDisabled || parentDisabled; - bool? adaptive = control.attrBool("adaptive") ?? parentAdaptive; - Widget? child = contentCtrls.isNotEmpty - ? createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: adaptive) + var bgColor = control.getColor("bgcolor", context); + var content = control.buildWidget("content"); + var ink = control.getBool("ink", false)!; + var onClick = control.getBool("on_click", false)!; + var onTapDown = control.getBool("on_tap_down", false)!; + var url = control.getString("url"); + var urlTarget = control.getString("url_target"); + var onLongPress = control.getBool("on_long_press", false)!; + var onHover = control.getBool("on_hover", false)!; + var ignoreInteractions = control.getBool("ignore_interactions", false)!; + var animation = control.getAnimation("animate"); + var blur = control.getBlur("blur"); + var colorFilter = control.getColorFilter("color_filter", Theme.of(context)); + var width = control.getDouble("width"); + var height = control.getDouble("height"); + var padding = control.getPadding("padding"); + var margin = control.getMargin("margin"); + var alignment = control.getAlignment("alignment"); + + var borderRadius = control.getBorderRadius("border_radius"); + var clipBehavior = control.getClipBehavior( + "clip_behavior", borderRadius != null ? Clip.antiAlias : Clip.none)!; + var decorationImage = control.getDecorationImage("image", context); + var boxDecoration = boxDecorationFromDetails( + shape: control.getBoxShape("shape", BoxShape.rectangle)!, + color: bgColor, + gradient: parseGradient(control.get("gradient"), Theme.of(context)), + borderRadius: borderRadius, + border: control.getBorder("border", Theme.of(context), + defaultSideColor: Theme.of(context).colorScheme.primary), + boxShadow: control.getBoxShadows("shadow", Theme.of(context)), + blendMode: control.getBlendMode("blend_mode"), + image: decorationImage, + ); + var boxForegroundDecoration = + parseBoxDecoration(control.get("foreground_decoration"), context); + Widget? container; + + var onAnimationEnd = control.getBool("on_animation_end", false)! + ? () => control.triggerEvent("animation_end" "container") : null; - - var animation = parseAnimation(control, "animate"); - var blur = parseBlur(control, "blur"); - var colorFilter = - parseColorFilter(control, "colorFilter", Theme.of(context)); - var width = control.attrDouble("width"); - var height = control.attrDouble("height"); - var padding = parseEdgeInsets(control, "padding"); - var margin = parseEdgeInsets(control, "margin"); - var alignment = parseAlignment(control, "alignment"); - - return withPageArgs((context, pageArgs) { - var borderRadius = parseBorderRadius(control, "borderRadius"); - var clipBehavior = parseClip(control.attrString("clipBehavior"), - borderRadius != null ? Clip.antiAlias : Clip.none)!; - var decorationImage = - parseDecorationImage(Theme.of(context), control, "image", pageArgs); - var boxDecoration = boxDecorationFromDetails( - shape: parseBoxShape(control.attrString("shape"), BoxShape.rectangle)!, - color: bgColor, - gradient: parseGradient(Theme.of(context), control, "gradient"), - borderRadius: borderRadius, - border: parseBorder(Theme.of(context), control, "border", - Theme.of(context).colorScheme.primary), - boxShadow: parseBoxShadow(Theme.of(context), control, "shadow"), - blendMode: parseBlendMode(control.attrString("blendMode")), - image: decorationImage, - ); - var boxForegroundDecoration = parseBoxDecoration( - Theme.of(context), control, "foregroundDecoration", pageArgs); - Widget? result; - - var onAnimationEnd = control.attrBool("onAnimationEnd", false)! - ? () { - backend.triggerControlEvent( - control.id, "animation_end", "container"); - } - : null; - if ((onClick || url != "" || onLongPress || onHover || onTapDown) && - ink && - !disabled) { - var ink = Material( - color: Colors.transparent, - borderRadius: boxDecoration!.borderRadius, - child: InkWell( - // Dummy callback to enable widget - // see https://github.com/flutter/flutter/issues/50116#issuecomment-582047374 - // and https://github.com/flutter/flutter/blob/eed80afe2c641fb14b82a22279d2d78c19661787/packages/flutter/lib/src/material/ink_well.dart#L1125-L1129 - onTap: onClick || url != "" || onTapDown - ? () { - debugPrint("Container ${control.id} clicked!"); - if (url != "") { - openWebBrowser(url, webWindowName: urlTarget); - } - if (onClick) { - backend.triggerControlEvent(control.id, "click"); - } - } - : null, - onTapDown: onTapDown - ? (details) { - backend.triggerControlEvent( - control.id, - "tap_down", - json.encode(ContainerTapEvent( - localX: details.localPosition.dx, - localY: details.localPosition.dy, - globalX: details.globalPosition.dx, - globalY: details.globalPosition.dy) - .toJson())); + if ((onClick || url != null || onLongPress || onHover || onTapDown) && + ink && + !control.disabled) { + var ink = Material( + color: Colors.transparent, + borderRadius: boxDecoration!.borderRadius, + child: InkWell( + // Dummy callback to enable widget + // see https://github.com/flutter/flutter/issues/50116#issuecomment-582047374 + // and https://github.com/flutter/flutter/blob/eed80afe2c641fb14b82a22279d2d78c19661787/packages/flutter/lib/src/material/ink_well.dart#L1125-L1129 + onTap: onClick || url != null || onTapDown + ? () { + if (url != null) { + openWebBrowser(url, webWindowName: urlTarget); } - : null, - onLongPress: onLongPress - ? () { - debugPrint("Container ${control.id} long pressed!"); - backend.triggerControlEvent(control.id, "long_press"); + if (onClick) { + control.triggerEvent("click"); } - : null, - onHover: onHover - ? (value) { - debugPrint("Container ${control.id} hovered!"); - backend.triggerControlEvent( - control.id, "hover", value.toString()); - } - : null, - borderRadius: borderRadius, - splashColor: control.attrColor("inkColor", context), - child: Container( - padding: padding, - alignment: alignment, - clipBehavior: Clip.none, - child: child, - ), - )); - - result = animation == null - ? Container( - width: width, - height: height, - margin: margin, - clipBehavior: clipBehavior, - decoration: boxDecoration, - foregroundDecoration: boxForegroundDecoration, - child: ink, - ) - : AnimatedContainer( - duration: animation.duration, - curve: animation.curve, - width: width, - height: height, - margin: margin, - decoration: boxDecoration, - foregroundDecoration: boxForegroundDecoration, - clipBehavior: clipBehavior, - onEnd: onAnimationEnd, - child: ink); - } else { - result = animation == null - ? Container( - width: width, - height: height, - margin: margin, - padding: padding, - alignment: alignment, - decoration: boxDecoration, - foregroundDecoration: boxForegroundDecoration, - clipBehavior: clipBehavior, - child: child) - : AnimatedContainer( - duration: animation.duration, - curve: animation.curve, - width: width, - height: height, - margin: margin, - padding: padding, - alignment: alignment, - decoration: boxDecoration, - foregroundDecoration: boxForegroundDecoration, - clipBehavior: clipBehavior, - onEnd: onAnimationEnd, - child: child); - - if ((onClick || onLongPress || onHover || onTapDown || url != "") && - !disabled) { - result = MouseRegion( - cursor: onClick || onTapDown || url != "" - ? SystemMouseCursors.click - : MouseCursor.defer, - onEnter: onHover - ? (value) { - debugPrint( - "Container's mouse region ${control.id} entered!"); - backend.triggerControlEvent(control.id, "hover", "true"); } : null, - onExit: onHover - ? (value) { - debugPrint( - "Container's mouse region ${control.id} exited!"); - backend.triggerControlEvent(control.id, "hover", "false"); + onTapDown: onTapDown + ? (TapDownDetails details) { + control.triggerEvent("tap_down", details.toMap()); } : null, - child: GestureDetector( - onTap: onClick || url != "" - ? () { - debugPrint("Container ${control.id} clicked!"); - if (url != "") { - openWebBrowser(url, webWindowName: urlTarget); - } - if (onClick) { - backend.triggerControlEvent(control.id, "click"); - } - } - : null, - onTapDown: onTapDown - ? (details) { - backend.triggerControlEvent( - control.id, - "tap_down", - json.encode(ContainerTapEvent( - localX: details.localPosition.dx, - localY: details.localPosition.dy, - globalX: details.globalPosition.dx, - globalY: details.globalPosition.dy) - .toJson())); + onLongPress: + onLongPress ? () => control.triggerEvent("long_press") : null, + onHover: onHover + ? (value) => control.triggerEvent("hover", value) + : null, + borderRadius: borderRadius, + splashColor: control.getColor("ink_color", context), + child: Container( + padding: padding, + alignment: alignment, + clipBehavior: Clip.none, + child: content, + ), + )); + + container = animation == null + ? Container( + width: width, + height: height, + margin: margin, + clipBehavior: clipBehavior, + decoration: boxDecoration, + foregroundDecoration: boxForegroundDecoration, + child: ink, + ) + : AnimatedContainer( + duration: animation.duration, + curve: animation.curve, + width: width, + height: height, + margin: margin, + decoration: boxDecoration, + foregroundDecoration: boxForegroundDecoration, + clipBehavior: clipBehavior, + onEnd: onAnimationEnd, + child: ink); + } else { + container = animation == null + ? Container( + width: width, + height: height, + margin: margin, + padding: padding, + alignment: alignment, + decoration: boxDecoration, + foregroundDecoration: boxForegroundDecoration, + clipBehavior: clipBehavior, + child: content) + : AnimatedContainer( + duration: animation.duration, + curve: animation.curve, + width: width, + height: height, + margin: margin, + padding: padding, + alignment: alignment, + decoration: boxDecoration, + foregroundDecoration: boxForegroundDecoration, + clipBehavior: clipBehavior, + onEnd: onAnimationEnd, + child: content); + + if ((onClick || onLongPress || onHover || onTapDown || url != null) && + !control.disabled) { + container = MouseRegion( + cursor: onClick || onTapDown || url != null + ? SystemMouseCursors.click + : MouseCursor.defer, + onEnter: onHover + ? (value) { + control.triggerEvent("hover", true); + } + : null, + onExit: onHover + ? (value) { + control.triggerEvent("hover", false); + } + : null, + child: GestureDetector( + onTap: onClick || url != null + ? () { + if (url != null) { + openWebBrowser(url, webWindowName: urlTarget); } - : null, - onLongPress: onLongPress - ? () { - debugPrint("Container ${control.id} clicked!"); - backend.triggerControlEvent(control.id, "long_press"); + if (onClick) { + control.triggerEvent("click"); } - : null, - child: result, - ), - ); - } - } - - if (blur != null) { - result = borderRadius != null - ? ClipRRect( - borderRadius: borderRadius, - child: BackdropFilter(filter: blur, child: result)) - : ClipRect(child: BackdropFilter(filter: blur, child: result)); - } - if (colorFilter != null) { - result = ColorFiltered(colorFilter: colorFilter, child: result); - } - - if (ignoreInteractions) { - result = IgnorePointer(child: result); + } + : null, + onTapDown: onTapDown + ? (TapDownDetails details) { + control.triggerEvent("tap_down", details.toMap()); + } + : null, + onLongPress: onLongPress + ? () { + control.triggerEvent("long_press"); + } + : null, + child: container, + ), + ); } + } - return constrainedControl(context, result, parent, control); - }); + if (blur != null) { + container = borderRadius != null + ? ClipRRect( + borderRadius: borderRadius, + child: BackdropFilter(filter: blur, child: container)) + : ClipRect(child: BackdropFilter(filter: blur, child: container)); + } + if (colorFilter != null) { + container = ColorFiltered(colorFilter: colorFilter, child: container); + } + + if (ignoreInteractions) { + container = IgnorePointer(child: container); + } + + return ConstrainedControl( + control: control, + child: container, + ); } } diff --git a/packages/flet/lib/src/controls/control_builder.dart b/packages/flet/lib/src/controls/control_builder.dart new file mode 100644 index 000000000..2576fe0b4 --- /dev/null +++ b/packages/flet/lib/src/controls/control_builder.dart @@ -0,0 +1,19 @@ +import 'package:flutter/widgets.dart'; + +import '../extensions/control.dart'; +import '../models/control.dart'; +import 'base_controls.dart'; + +class ControlBuilderControl extends StatelessWidget { + final Control control; + + const ControlBuilderControl({super.key, required this.control}); + + @override + Widget build(BuildContext context) { + debugPrint("ControlBuilder.build: ${control.id}"); + return BaseControl( + control: control, + child: control.buildWidget("content") ?? const SizedBox.shrink()); + } +} diff --git a/packages/flet/lib/src/controls/control_widget.dart b/packages/flet/lib/src/controls/control_widget.dart new file mode 100644 index 000000000..8b1e331e2 --- /dev/null +++ b/packages/flet/lib/src/controls/control_widget.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../extensions/control.dart'; +import '../flet_backend.dart'; +import '../models/control.dart'; +import '../utils/keys.dart'; +import '../utils/numbers.dart'; +import '../utils/theme.dart'; +import '../widgets/control_inherited_notifier.dart'; +import '../widgets/error.dart'; + +class ControlWidget extends StatelessWidget { + final Control control; + + const ControlWidget({super.key, required this.control}); + + @override + Widget build(BuildContext context) { + ControlKey? controlKey = control.getKey("key"); + Key? key; + if (controlKey is ControlScrollKey) { + key = GlobalKey(); + FletBackend.of(context).globalKeys[controlKey.toString()] = + key as GlobalKey; + } else if (controlKey != null) { + key = ValueKey(controlKey.value); + } + + Widget? widget; + if (control.internals?["skip_inherited_notifier"] == true) { + for (var extension in FletBackend.of(context).extensions) { + widget = extension.createWidget(key, control); + if (widget != null) return widget; + } + widget = ErrorControl("Unknown control: ${control.type}"); + } else { + widget = ControlInheritedNotifier( + notifier: control, + child: Builder(builder: (context) { + ControlInheritedNotifier.of(context); + + Widget? cw; + for (var extension in FletBackend.of(context).extensions) { + cw = extension.createWidget(key, control); + if (cw != null) return cw; + } + + return ErrorControl("Unknown control: ${control.type}"); + }), + ); + } + + // Return original widget if no theme is defined + final isRootControl = control == FletBackend.of(context).page; + final hasNoThemes = control.getString("theme") == null && + control.getString("dark_theme") == null; + final themeMode = control.getThemeMode("theme_mode"); + + if (isRootControl || (hasNoThemes && themeMode == null)) { + return widget; + } + + // Wrap in Theme widget + final ThemeData? parentTheme = + (themeMode == null) ? Theme.of(context) : null; + + Widget buildTheme(Brightness? brightness) { + final themeProp = brightness == Brightness.dark ? "dark_theme" : "theme"; + final themeData = parseTheme(control.get(themeProp), context, brightness, + parentTheme: parentTheme); + return Theme(data: themeData, child: widget!); + } + + if (themeMode == ThemeMode.system) { + final brightness = context.select( + (backend) => backend.platformBrightness, + ); + return buildTheme(brightness); + } + + if (themeMode == ThemeMode.light) { + return buildTheme(Brightness.light); + } else if (themeMode == ThemeMode.dark) { + return buildTheme(Brightness.dark); + } else { + return buildTheme(parentTheme?.brightness); + } + } +} diff --git a/packages/flet/lib/src/controls/create_control.dart b/packages/flet/lib/src/controls/create_control.dart deleted file mode 100644 index 190d3ec21..000000000 --- a/packages/flet/lib/src/controls/create_control.dart +++ /dev/null @@ -1,1298 +0,0 @@ -import 'dart:math'; - -import 'package:flet/src/utils/badge.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_redux/flutter_redux.dart'; - -import '../control_factory.dart'; -import '../flet_app_services.dart'; -import '../flet_control_backend.dart'; -import '../models/app_state.dart'; -import '../models/control.dart'; -import '../models/control_view_model.dart'; -import '../models/page_media_view_model.dart'; -import '../utils/animations.dart'; -import '../utils/theme.dart'; -import '../utils/tooltip.dart'; -import '../utils/transforms.dart'; -import 'alert_dialog.dart'; -import 'animated_switcher.dart'; -import 'auto_complete.dart'; -import 'autofill_group.dart'; -import 'banner.dart'; -import 'barchart.dart'; -import 'bottom_app_bar.dart'; -import 'bottom_sheet.dart'; -import 'canvas.dart'; -import 'card.dart'; -import 'checkbox.dart'; -import 'chip.dart'; -import 'circle_avatar.dart'; -import 'column.dart'; -import 'container.dart'; -import 'cupertino_action_sheet.dart'; -import 'cupertino_action_sheet_action.dart'; -import 'cupertino_activity_indicator.dart'; -import 'cupertino_alert_dialog.dart'; -import 'cupertino_bottom_sheet.dart'; -import 'cupertino_button.dart'; -import 'cupertino_checkbox.dart'; -import 'cupertino_context_menu.dart'; -import 'cupertino_context_menu_action.dart'; -import 'cupertino_date_picker.dart'; -import 'cupertino_dialog_action.dart'; -import 'cupertino_list_tile.dart'; -import 'cupertino_navigation_bar.dart'; -import 'cupertino_picker.dart'; -import 'cupertino_radio.dart'; -import 'cupertino_segmented_button.dart'; -import 'cupertino_slider.dart'; -import 'cupertino_sliding_segmented_button.dart'; -import 'cupertino_switch.dart'; -import 'cupertino_textfield.dart'; -import 'cupertino_timer_picker.dart'; -import 'datatable.dart'; -import 'date_picker.dart'; -import 'dismissible.dart'; -import 'divider.dart'; -import 'drag_target.dart'; -import 'draggable.dart'; -import 'dropdown.dart'; -import 'dropdownm2.dart'; -import 'elevated_button.dart'; -import 'error.dart'; -import 'expansion_panel.dart'; -import 'expansion_tile.dart'; -import 'file_picker.dart'; -import 'flet_app_control.dart'; -import 'floating_action_button.dart'; -import 'gesture_detector.dart'; -import 'grid_view.dart'; -import 'haptic_feedback.dart'; -import 'icon.dart'; -import 'icon_button.dart'; -import 'image.dart'; -import 'interactive_viewer.dart'; -import 'linechart.dart'; -import 'list_tile.dart'; -import 'list_view.dart'; -import 'markdown.dart'; -import 'menu_bar.dart'; -import 'menu_item_button.dart'; -import 'merge_semantics.dart'; -import 'navigation_bar.dart'; -import 'navigation_rail.dart'; -import 'outlined_button.dart'; -import 'page.dart'; -import 'pagelet.dart'; -import 'piechart.dart'; -import 'placeholder.dart'; -import 'popup_menu_button.dart'; -import 'progress_bar.dart'; -import 'progress_ring.dart'; -import 'radio.dart'; -import 'radio_group.dart'; -import 'range_slider.dart'; -import 'reorderable_draggable.dart'; -import 'reorderable_list_view.dart'; -import 'responsive_row.dart'; -import 'row.dart'; -import 'safe_area.dart'; -import 'search_anchor.dart'; -import 'segmented_button.dart'; -import 'selection_area.dart'; -import 'semantics.dart'; -import 'semantics_service.dart'; -import 'shader_mask.dart'; -import 'shake_detector.dart'; -import 'slider.dart'; -import 'snack_bar.dart'; -import 'stack.dart'; -import 'submenu_button.dart'; -import 'switch.dart'; -import 'tabs.dart'; -import 'text.dart'; -import 'text_button.dart'; -import 'textfield.dart'; -import 'time_picker.dart'; -import 'transparent_pointer.dart'; -import 'vertical_divider.dart'; - -Widget createControl(Control? parent, String id, bool parentDisabled, - {Widget? nextChild, bool? parentAdaptive}) { - //debugPrint("createControl(): $id"); - return StoreConnector( - key: ValueKey(id), - distinct: true, - converter: (store) { - //debugPrint("ControlViewModel $id converter"); - return ControlViewModel.fromStore(store, id); - }, - // onWillChange: (prev, next) { - // debugPrint("onWillChange() $id: $prev, $next"); - // }, - ignoreChange: (state) { - //debugPrint("ignoreChange: $id"); - return state.controls[id] == null; - }, - builder: (context, controlView) { - if (controlView == null) { - return const SizedBox.shrink(); - } - - Key? controlKey; - var key = controlView.control.attrString("key", "")!; - if (key != "") { - if (key.startsWith("test:")) { - controlKey = Key(key.substring(5)); - } else { - var globalKey = controlKey = GlobalKey(); - FletAppServices.of(context).globalKeys[key] = globalKey; - } - } - - Widget? widget; - - for (var createControlFactory - in FletAppServices.of(context).createControlFactories) { - widget = createControlFactory(CreateControlArgs( - controlKey, - parent, - controlView.control, - controlView.children, - nextChild, - parentDisabled, - parentAdaptive, - FletAppServices.of(context).server)); - if (widget != null) { - break; - } - } - - // try creating Flet built-in widget - widget ??= createWidget(controlKey, controlView, parent, parentDisabled, - parentAdaptive, nextChild, FletAppServices.of(context).server); - - // no theme defined? return widget - var themeMode = - parseThemeMode(controlView.control.attrString("themeMode")); - if (id == "page" || - (controlView.control.attrString("theme") == null && - controlView.control.attrString("darkTheme") == null && - themeMode == null)) { - return widget; - } - - // wrap into theme widget - ThemeData? parentTheme = (themeMode == null) ? Theme.of(context) : null; - buildTheme(Brightness? brightness) { - return Theme( - data: parseTheme( - controlView.control, - brightness == Brightness.dark ? "darkTheme" : "theme", - brightness, - parentTheme: parentTheme), - child: widget!); - } - - if (themeMode == ThemeMode.system) { - return StoreConnector( - distinct: true, - converter: (store) => PageMediaViewModel.fromStore(store), - builder: (context, media) { - return buildTheme(media.displayBrightness); - }); - } else { - return buildTheme(themeMode == ThemeMode.light - ? Brightness.light - : themeMode == ThemeMode.dark - ? Brightness.dark - : parentTheme?.brightness); - } - }, - ); -} - -Widget createWidget( - Key? key, - ControlViewModel controlView, - Control? parent, - bool parentDisabled, - bool? parentAdaptive, - Widget? nextChild, - FletControlBackend backend) { - switch (controlView.control.type.toLowerCase()) { - case "page": - return PageControl( - control: controlView.control, - children: controlView.children, - dispatch: controlView.dispatch, - backend: backend); - case "text": - return TextControl( - key: key, - parent: parent, - control: controlView.control, - parentDisabled: parentDisabled, - backend: backend); - case "icon": - return IconControl( - key: key, parent: parent, control: controlView.control); - case "filepicker": - return FilePickerControl( - parent: parent, - control: controlView.control, - nextChild: nextChild, - backend: backend); - case "markdown": - return MarkdownControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - backend: backend); - case "fletapp": - return FletAppControl( - key: key, parent: parent, control: controlView.control); - case "image": - return ImageControl( - key: key, - parent: parent, - children: controlView.children, - control: controlView.control, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "divider": - return DividerControl( - key: key, parent: parent, control: controlView.control); - case "selectionarea": - return SelectionAreaControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "hapticfeedback": - return HapticFeedbackControl( - parent: parent, - control: controlView.control, - nextChild: nextChild, - backend: backend); - case "shakedetector": - return ShakeDetectorControl( - parent: parent, - control: controlView.control, - nextChild: nextChild, - backend: backend); - case "verticaldivider": - return VerticalDividerControl( - key: key, parent: parent, control: controlView.control); - case "circleavatar": - return CircleAvatarControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - backend: backend); - case "chip": - return ChipControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "pagelet": - return PageletControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "progressring": - return ProgressRingControl( - key: key, parent: parent, control: controlView.control); - case "progressbar": - return ProgressBarControl( - key: key, parent: parent, control: controlView.control); - case "elevatedbutton": - case "filledbutton": - case "filledtonalbutton": - return ElevatedButtonControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "cupertinobutton": - return CupertinoButtonControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "outlinedbutton": - return OutlinedButtonControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "textbutton": - return TextButtonControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "cupertinodialogaction": - return CupertinoDialogActionControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "iconbutton": - return IconButtonControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "floatingactionbutton": - return FloatingActionButtonControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "popupmenubutton": - return PopupMenuButtonControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - backend: backend); - case "column": - return ColumnControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "row": - return RowControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "responsiverow": - return ResponsiveRowControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "menubar": - return MenuBarControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive); - case "submenubutton": - return SubMenuButtonControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - backend: backend); - case "menuitembutton": - return MenuItemButtonControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "placeholder": - return PlaceholderControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive); - case "cupertinoslidingsegmentedbutton": - return CupertinoSlidingSegmentedButtonControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentAdaptive: parentAdaptive, - parentDisabled: parentDisabled, - backend: backend); - case "segmentedbutton": - return SegmentedButtonControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - backend: backend); - case "cupertinosegmentedbutton": - return CupertinoSegmentedButtonControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentAdaptive: parentAdaptive, - parentDisabled: parentDisabled, - backend: backend); - case "expansionpanellist": - return ExpansionPanelListControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "stack": - return StackControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive); - case "container": - return ContainerControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "datepicker": - return DatePickerControl( - parent: parent, - control: controlView.control, - parentDisabled: parentDisabled, - backend: backend); - case "cupertinodatepicker": - return CupertinoDatePickerControl( - parent: parent, - control: controlView.control, - parentDisabled: parentDisabled, - backend: backend); - case "timepicker": - return TimePickerControl( - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - backend: backend); - case "cupertinotimerpicker": - return CupertinoTimerPickerControl( - parent: parent, - control: controlView.control, - parentDisabled: parentDisabled, - backend: backend); - case "cupertinopicker": - return CupertinoPickerControl( - parent: parent, - control: controlView.control, - children: controlView.children, - parentAdaptive: parentAdaptive, - parentDisabled: parentDisabled, - backend: backend); - case "cupertinobottomsheet": - return CupertinoBottomSheetControl( - parent: parent, - control: controlView.control, - children: controlView.children, - parentAdaptive: parentAdaptive, - parentDisabled: parentDisabled, - nextChild: nextChild, - backend: backend); - case "draggable": - return DraggableControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "dragtarget": - return DragTargetControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "card": - return CardControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive); - case "safearea": - return SafeAreaControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive); - case "datatable": - return DataTableControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - backend: backend); - case "transparentpointer": - return TransparentPointerControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive); - case "gesturedetector": - return GestureDetectorControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "semantics": - return SemanticsControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "mergesemantics": - return MergeSemanticsControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive); - case "semanticsservice": - return SemanticsServiceControl( - parent: parent, control: controlView.control, backend: backend); - case "shadermask": - return ShaderMaskControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive); - case "animatedswitcher": - return AnimatedSwitcherControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive); - case "listtile": - return ListTileControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "interactiveviewer": - return InteractiveViewerControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "cupertinolisttile": - return CupertinoListTileControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "cupertinoactionsheet": - return CupertinoActionSheetControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "cupertinoactionsheetaction": - return CupertinoActionSheetActionControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "expansiontile": - return ExpansionTileControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "listview": - return ListViewControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "reorderablelistview": - return ReorderableListViewControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "reorderabledraggable": - return ReorderableDraggableControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - ); - case "gridview": - return GridViewControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "autocomplete": - return AutoCompleteControl( - key: key, - parent: parent, - control: controlView.control, - backend: backend); - case "textfield": - return TextFieldControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "cupertinotextfield": - return CupertinoTextFieldControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "searchbar": - return SearchAnchorControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "checkbox": - return CheckboxControl( - key: key, - parent: parent, - control: controlView.control, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - children: controlView.children, - backend: backend); - case "cupertinocheckbox": - return CupertinoCheckboxControl( - key: key, - parent: parent, - control: controlView.control, - parentDisabled: parentDisabled, - backend: backend); - case "switch": - return SwitchControl( - key: key, - parent: parent, - control: controlView.control, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - children: controlView.children, - backend: backend); - case "cupertinoswitch": - return CupertinoSwitchControl( - key: key, - parent: parent, - control: controlView.control, - parentDisabled: parentDisabled, - backend: backend); - case "slider": - return SliderControl( - key: key, - parent: parent, - control: controlView.control, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "cupertinoslider": - return CupertinoSliderControl( - key: key, - parent: parent, - control: controlView.control, - parentDisabled: parentDisabled, - backend: backend); - case "rangeslider": - return RangeSliderControl( - key: key, - parent: parent, - control: controlView.control, - parentDisabled: parentDisabled, - backend: backend); - case "radiogroup": - return RadioGroupControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive); - case "radio": - return RadioControl( - key: key, - parent: parent, - control: controlView.control, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "autofillgroup": - return AutofillGroupControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive); - case "cupertinoradio": - return CupertinoRadioControl( - key: key, - parent: parent, - control: controlView.control, - parentDisabled: parentDisabled, - backend: backend); - case "dropdown": - return DropdownControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "dropdownm2": - return DropdownM2Control( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "snackbar": - return SnackBarControl( - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - nextChild: nextChild, - backend: backend); - case "dismissible": - return DismissibleControl( - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "cupertinoactivityindicator": - return CupertinoActivityIndicatorControl( - key: key, - parent: parent, - control: controlView.control, - parentDisabled: parentDisabled, - backend: backend); - case "alertdialog": - return AlertDialogControl( - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - nextChild: nextChild, - backend: backend); - case "cupertinocontextmenu": - return CupertinoContextMenuControl( - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "cupertinocontextmenuaction": - return CupertinoContextMenuActionControl( - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "cupertinoalertdialog": - return CupertinoAlertDialogControl( - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - nextChild: nextChild, - backend: backend); - case "bottomsheet": - return BottomSheetControl( - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - nextChild: nextChild, - backend: backend); - case "banner": - return BannerControl( - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - nextChild: nextChild, - backend: backend); - case "tabs": - return TabsControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "navigationrail": - return NavigationRailControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "navigationbar": - return NavigationBarControl( - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "cupertinonavigationbar": - return CupertinoNavigationBarControl( - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - case "bottomappbar": - return BottomAppBarControl( - parent: parent, - control: controlView.control, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - children: controlView.children, - ); - case "linechart": - return LineChartControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - backend: backend); - case "barchart": - return BarChartControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - backend: backend); - case "piechart": - return PieChartControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - backend: backend); - case "canvas": - return CanvasControl( - key: key, - parent: parent, - control: controlView.control, - children: controlView.children, - parentDisabled: parentDisabled, - parentAdaptive: parentAdaptive, - backend: backend); - default: - return ErrorControl("Unknown control: ${controlView.control.type}"); - } -} - -Widget baseControl( - BuildContext context, Widget widget, Control? parent, Control control) { - return _expandable( - _directionality( - _tooltip( - _opacity(context, widget, parent, control), - Theme.of(context), - parent, - control, - ), - parent, - control), - parent, - control); -} - -Widget constrainedControl( - BuildContext context, Widget widget, Control? parent, Control control) { - return _expandable( - _badge( - _positionedControl( - context, - _aspectRatio( - _offsetControl( - context, - _scaledControl( - context, - _rotatedControl( - context, - _sizedControl( - _directionality( - _tooltip( - _opacity( - context, widget, parent, control), - Theme.of(context), - parent, - control), - parent, - control), - parent, - control), - parent, - control), - parent, - control), - parent, - control), - parent, - control), - parent, - control), - Theme.of(context), - parent, - control), - parent, - control); -} - -Widget _opacity( - BuildContext context, Widget widget, Control? parent, Control control) { - var opacity = control.attrDouble("opacity"); - var animation = parseAnimation(control, "animateOpacity"); - if (animation != null) { - return AnimatedOpacity( - duration: animation.duration, - curve: animation.curve, - opacity: opacity ?? 1.0, - onEnd: control.attrBool("onAnimationEnd", false)! - ? () { - FletAppServices.of(context) - .server - .triggerControlEvent(control.id, "animation_end", "opacity"); - } - : null, - child: widget, - ); - } else if (opacity != null) { - return Opacity( - opacity: opacity, - child: widget, - ); - } - return widget; -} - -Widget _tooltip( - Widget widget, ThemeData theme, Control? parent, Control control) { - var tooltip = parseTooltip(control, "tooltip", widget, theme); - return tooltip ?? widget; -} - -Widget _badge( - Widget widget, ThemeData theme, Control? parent, Control control) { - var badge = parseBadge(control, "badge", widget, theme); - return badge ?? widget; -} - -Widget _aspectRatio(Widget widget, Control? parent, Control control) { - var aspectRatio = control.attrDouble("aspectRatio"); - return aspectRatio != null - ? AspectRatio( - aspectRatio: aspectRatio, - child: widget, - ) - : widget; -} - -Widget _rotatedControl( - BuildContext context, Widget widget, Control? parent, Control control) { - var rotationDetails = parseRotate(control, "rotate"); - var animation = parseAnimation(control, "animateRotation"); - if (animation != null) { - return AnimatedRotation( - turns: rotationDetails != null ? rotationDetails.angle / (2 * pi) : 0, - alignment: rotationDetails?.alignment ?? Alignment.center, - duration: animation.duration, - curve: animation.curve, - onEnd: control.attrBool("onAnimationEnd", false)! - ? () { - FletAppServices.of(context).server.triggerControlEvent( - control.id, "animation_end", "rotation"); - } - : null, - child: widget); - } else if (rotationDetails != null) { - return Transform.rotate( - angle: rotationDetails.angle, - alignment: rotationDetails.alignment, - child: widget); - } - return widget; -} - -Widget _scaledControl( - BuildContext context, Widget widget, Control? parent, Control control) { - var scaleDetails = parseScale(control, "scale"); - var animation = parseAnimation(control, "animateScale"); - if (animation != null) { - return AnimatedScale( - scale: scaleDetails?.scale ?? 1.0, - alignment: scaleDetails?.alignment ?? Alignment.center, - duration: animation.duration, - curve: animation.curve, - onEnd: control.attrBool("onAnimationEnd", false)! - ? () { - FletAppServices.of(context) - .server - .triggerControlEvent(control.id, "animation_end", "scale"); - } - : null, - child: widget); - } else if (scaleDetails != null) { - return Transform.scale( - scale: scaleDetails.scale, - scaleX: scaleDetails.scaleX, - scaleY: scaleDetails.scaleY, - alignment: scaleDetails.alignment, - child: widget); - } - return widget; -} - -Widget _offsetControl( - BuildContext context, Widget widget, Control? parent, Control control) { - var offset = parseOffset(control, "offset"); - var animation = parseAnimation(control, "animateOffset"); - if (offset != null && animation != null) { - return AnimatedSlide( - offset: offset, - duration: animation.duration, - curve: animation.curve, - onEnd: control.attrBool("onAnimationEnd", false)! - ? () { - FletAppServices.of(context) - .server - .triggerControlEvent(control.id, "animation_end", "offset"); - } - : null, - child: widget); - } else if (offset != null) { - return FractionalTranslation(translation: offset, child: widget); - } - return widget; -} - -Widget _positionedControl( - BuildContext context, Widget widget, Control? parent, Control control) { - var left = control.attrDouble("left", null); - var top = control.attrDouble("top", null); - var right = control.attrDouble("right", null); - var bottom = control.attrDouble("bottom", null); - - var animation = parseAnimation(control, "animatePosition"); - if (animation != null) { - if (left == null && top == null && right == null && bottom == null) { - left = 0; - top = 0; - } - - return AnimatedPositioned( - duration: animation.duration, - curve: animation.curve, - left: left, - top: top, - right: right, - bottom: bottom, - onEnd: control.attrBool("onAnimationEnd", false)! - ? () { - FletAppServices.of(context) - .server - .triggerControlEvent(control.id, "animation_end", "position"); - } - : null, - child: widget, - ); - } else if (left != null || top != null || right != null || bottom != null) { - if (parent?.type != "stack" && parent?.type != "page") { - return ErrorControl("Error displaying ${control.type}", - description: - "Control can be positioned absolutely with \"left\", \"top\", \"right\" and \"bottom\" properties inside Stack control only."); - } - return Positioned( - left: left, - top: top, - right: right, - bottom: bottom, - child: widget, - ); - } - return widget; -} - -Widget _sizedControl(Widget widget, Control? parent, Control control) { - var width = control.attrDouble("width"); - var height = control.attrDouble("height"); - if ((width != null || height != null) && - !["container", "image"].contains(control.type)) { - widget = ConstrainedBox( - constraints: BoxConstraints.tightFor(width: width, height: height), - child: widget, - ); - } - var animation = parseAnimation(control, "animateSize"); - if (animation != null) { - return AnimatedSize( - duration: animation.duration, curve: animation.curve, child: widget); - } - return widget; -} - -Widget _expandable(Widget widget, Control? parent, Control control) { - if (parent != null && ["view", "column", "row"].contains(parent.type)) { - int? expand = control.attrInt("expand"); - var expandLoose = control.attrBool("expandLoose"); - return expand != null - ? (expandLoose == true) - ? Flexible(flex: expand, child: widget) - : Expanded(flex: expand, child: widget) - : widget; - } - return widget; -} - -Widget _directionality(Widget widget, Control? parent, Control control) { - bool rtl = control.attrBool("rtl", false)!; - return rtl - ? Directionality(textDirection: TextDirection.rtl, child: widget) - : widget; -} diff --git a/packages/flet/lib/src/controls/cupertino_action_sheet.dart b/packages/flet/lib/src/controls/cupertino_action_sheet.dart index f0f5755a4..7b38c5222 100644 --- a/packages/flet/lib/src/controls/cupertino_action_sheet.dart +++ b/packages/flet/lib/src/controls/cupertino_action_sheet.dart @@ -1,69 +1,25 @@ import 'package:flutter/cupertino.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import 'create_control.dart'; +import 'base_controls.dart'; -class CupertinoActionSheetControl extends StatefulWidget { - final Control? parent; +class CupertinoActionSheetControl extends StatelessWidget { final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const CupertinoActionSheetControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const CupertinoActionSheetControl({super.key, required this.control}); @override - State createState() => - _CupertinoActionSheetControlState(); -} - -class _CupertinoActionSheetControlState - extends State { - @override Widget build(BuildContext context) { - debugPrint("CupertinoActionSheetControl build: ${widget.control.id}"); - - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - var titleCtrls = - widget.children.where((c) => c.name == "title" && c.isVisible); - var messageCtrls = - widget.children.where((c) => c.name == "message" && c.isVisible); - var cancelButtonCtrls = - widget.children.where((c) => c.name == "cancel" && c.isVisible); - var actionCtrls = - widget.children.where((c) => c.name == "action" && c.isVisible); + debugPrint("CupertinoActionSheetControl build: ${control.id}"); var sheet = CupertinoActionSheet( - title: titleCtrls.isNotEmpty - ? createControl(widget.control, titleCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : null, - message: messageCtrls.isNotEmpty - ? createControl(widget.control, messageCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : null, - cancelButton: cancelButtonCtrls.isNotEmpty - ? createControl(widget.control, cancelButtonCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : null, - actions: actionCtrls.isNotEmpty - ? actionCtrls - .map((c) => createControl(widget.control, c.id, disabled, - parentAdaptive: widget.parentAdaptive)) - .toList() - : null, + title: control.buildTextOrWidget("title"), + message: control.buildTextOrWidget("message"), + cancelButton: control.buildWidget("cancel"), + actions: control.buildWidgets("actions"), ); - return constrainedControl(context, sheet, widget.parent, widget.control); + return ConstrainedControl(control: control, child: sheet); } } diff --git a/packages/flet/lib/src/controls/cupertino_action_sheet_action.dart b/packages/flet/lib/src/controls/cupertino_action_sheet_action.dart index 99339b277..fae8aa58d 100644 --- a/packages/flet/lib/src/controls/cupertino_action_sheet_action.dart +++ b/packages/flet/lib/src/controls/cupertino_action_sheet_action.dart @@ -1,58 +1,39 @@ import 'package:flutter/cupertino.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/mouse.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; class CupertinoActionSheetActionControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const CupertinoActionSheetActionControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const CupertinoActionSheetActionControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("CupertinoActionSheetActionControl build: ${control.id}"); - bool disabled = control.isDisabled || parentDisabled; - var text = control.attrString("text"); - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - if (contentCtrls.isEmpty && text == null) { + var content = control.buildTextOrWidget("content"); + if (content == null) { return const ErrorControl( - "CupertinoActionSheetAction must have at minimum text or (visible) content provided"); + "CupertinoActionSheetAction.content must be a string or visible Control"); } - return constrainedControl( - context, - CupertinoActionSheetAction( - isDefaultAction: control.attrBool("isDefaultAction", false)!, - isDestructiveAction: control.attrBool("isDestructiveAction", false)!, - onPressed: () { - if (!disabled) { - backend.triggerControlEvent(control.id, "click"); - } - }, - mouseCursor: parseMouseCursor(control.attrString("mouseCursor")), - child: contentCtrls.isNotEmpty - ? createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: parentAdaptive) - : Text(text!), - ), - parent, - control); + final actionSheet = CupertinoActionSheetAction( + isDefaultAction: control.getBool("default", false)!, + isDestructiveAction: control.getBool("destructive", false)!, + onPressed: () { + if (!control.disabled) { + control.triggerEvent("click"); + } + }, + mouseCursor: control.getMouseCursor("mouse_cursor"), + child: content, + ); + + return ConstrainedControl(control: control, child: actionSheet); } } diff --git a/packages/flet/lib/src/controls/cupertino_activity_indicator.dart b/packages/flet/lib/src/controls/cupertino_activity_indicator.dart index 7329cfa05..0c024f616 100644 --- a/packages/flet/lib/src/controls/cupertino_activity_indicator.dart +++ b/packages/flet/lib/src/controls/cupertino_activity_indicator.dart @@ -1,34 +1,26 @@ import 'package:flutter/cupertino.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; -import 'create_control.dart'; +import '../utils/colors.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; class CupertinoActivityIndicatorControl extends StatelessWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final FletControlBackend backend; - const CupertinoActivityIndicatorControl( - {super.key, - this.parent, - required this.control, - required this.parentDisabled, - required this.backend}); + const CupertinoActivityIndicatorControl({ + super.key, + required this.control, + }); @override Widget build(BuildContext context) { debugPrint("CupertinoActivityIndicatorControl build: ${control.id}"); - - return constrainedControl( - context, - CupertinoActivityIndicator( - radius: control.attrDouble("radius", 10)!, - animating: control.attrBool("animating", true)!, - color: control.attrColor("color", context), - ), - parent, - control); + final activityIndicator = CupertinoActivityIndicator( + radius: control.getDouble("radius", 10)!, + animating: control.getBool("animating", true)!, + color: control.getColor("color", context), + ); + return ConstrainedControl(control: control, child: activityIndicator); } } diff --git a/packages/flet/lib/src/controls/cupertino_alert_dialog.dart b/packages/flet/lib/src/controls/cupertino_alert_dialog.dart index 0ba0b23ef..78bcb61a8 100644 --- a/packages/flet/lib/src/controls/cupertino_alert_dialog.dart +++ b/packages/flet/lib/src/controls/cupertino_alert_dialog.dart @@ -1,30 +1,18 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/animations.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/colors.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; class CupertinoAlertDialogControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final Widget? nextChild; - final FletControlBackend backend; - - const CupertinoAlertDialogControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.nextChild, - required this.backend}); + + CupertinoAlertDialogControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => @@ -33,21 +21,43 @@ class CupertinoAlertDialogControl extends StatefulWidget { class _CupertinoAlertDialogControlState extends State { + Widget? _dialog; + bool _open = false; + NavigatorState? _navigatorState; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + debugPrint( + "CupertinoAlertDialog.didChangeDependencies: ${widget.control.id}"); + _navigatorState = Navigator.of(context); + _toggleDialog(); + } + + @override + void didUpdateWidget(covariant CupertinoAlertDialogControl oldWidget) { + debugPrint("CupertinoAlertDialog.didUpdateWidget: ${widget.control.id}"); + super.didUpdateWidget(oldWidget); + _toggleDialog(); + } + + @override + void dispose() { + debugPrint("CupertinoAlertDialog.dispose: ${widget.control.id}"); + _closeDialog(); + super.dispose(); + } + Widget _createCupertinoAlertDialog() { - bool disabled = widget.control.isDisabled || widget.parentDisabled; - var titleCtrls = - widget.children.where((c) => c.name == "title" && c.isVisible); - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); - var actionCtrls = - widget.children.where((c) => c.name == "action" && c.isVisible); - if (titleCtrls.isEmpty && contentCtrls.isEmpty && actionCtrls.isEmpty) { + var title = widget.control.buildTextOrWidget("title"); + var content = widget.control.buildWidget("content"); + var actions = widget.control.buildWidgets("actions"); + if (title == null && content == null && actions.isEmpty) { return const ErrorControl( "CupertinoAlertDialog has nothing to display. Provide at minimum one of the following: title, content, actions"); } var insetAnimation = parseAnimation( - widget.control, - "insetAnimation", + widget.control.get("inset_animation"), ImplicitAnimationDetails( duration: const Duration(milliseconds: 100), curve: Curves.decelerate))!; @@ -55,71 +65,64 @@ class _CupertinoAlertDialogControlState return CupertinoAlertDialog( insetAnimationCurve: insetAnimation.curve, insetAnimationDuration: insetAnimation.duration, - title: titleCtrls.isNotEmpty - ? createControl(widget.control, titleCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : null, - content: contentCtrls.isNotEmpty - ? createControl(widget.control, contentCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : null, - actions: actionCtrls - .map((c) => createControl(widget.control, c.id, disabled, - parentAdaptive: widget.parentAdaptive)) - .toList(), + title: title, + content: content, + actions: actions, ); } - @override - Widget build(BuildContext context) { - debugPrint("CupertinoAlertDialog build ($hashCode): ${widget.control.id}"); - - bool lastOpen = widget.control.state["open"] ?? false; - + void _toggleDialog() { debugPrint("CupertinoAlertDialog build: ${widget.control.id}"); - var open = widget.control.attrBool("open", false)!; - var modal = widget.control.attrBool("modal", false)!; + var open = widget.control.getBool("open", false)!; + var modal = widget.control.getBool("modal", false)!; - debugPrint("Current open state: $lastOpen"); - debugPrint("New open state: $open"); + if (open && (open != _open)) { + _dialog = _createCupertinoAlertDialog(); - if (open && (open != lastOpen)) { - var dialog = _createCupertinoAlertDialog(); - if (dialog is ErrorControl) { - return dialog; + if (_dialog is ErrorControl) { + debugPrint( + "CupertinoAlertDialog: ErrorControl, not showing dialog: ${(_dialog as ErrorControl).message}"); + return; } - // close previous dialog - if (ModalRoute.of(context)?.isCurrent != true) { - Navigator.of(context).pop(); - } - - widget.control.state["open"] = open; + _open = open; WidgetsBinding.instance.addPostFrameCallback((_) { showDialog( barrierDismissible: !modal, - barrierColor: widget.control.attrColor("barrierColor", context), + barrierColor: widget.control.getColor("barrier_color", context), useRootNavigator: false, context: context, - builder: (context) => dialog).then((value) { - lastOpen = widget.control.state["open"] ?? false; - debugPrint("Dialog should be dismissed ($hashCode): $lastOpen"); - bool shouldDismiss = lastOpen; - widget.control.state["open"] = false; - - if (shouldDismiss) { - widget.backend - .updateControlState(widget.control.id, {"open": "false"}); - widget.backend.triggerControlEvent(widget.control.id, "dismiss"); - } + builder: (context) => _dialog!).then((value) { + debugPrint("Dismissing CupertinoAlertDialog(${widget.control.id})"); + _open = false; + widget.control.updateProperties({"open": false}); + widget.control.triggerEvent("dismiss"); }); }); - } else if (open != lastOpen && lastOpen) { - Navigator.of(context).pop(); + } else if (!open && _open) { + _closeDialog(); + } + } + + void _closeDialog() { + if (_open) { + if (_navigatorState?.canPop() == true) { + debugPrint( + "CupertinoAlertDialog(${widget.control.id}): Closing dialog managed by this widget."); + _navigatorState?.pop(); + _open = false; + _dialog = null; + } else { + debugPrint( + "CupertinoAlertDialog(${widget.control.id}): Dialog was not opened by this widget, skipping pop."); + } } + } - return widget.nextChild ?? const SizedBox.shrink(); + @override + Widget build(BuildContext context) { + return _dialog is ErrorControl ? _dialog! : const SizedBox.shrink(); } } diff --git a/packages/flet/lib/src/controls/cupertino_app_bar.dart b/packages/flet/lib/src/controls/cupertino_app_bar.dart index 8672de643..b6a097d01 100644 --- a/packages/flet/lib/src/controls/cupertino_app_bar.dart +++ b/packages/flet/lib/src/controls/cupertino_app_bar.dart @@ -1,113 +1,60 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/borders.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; -import '../utils/overlay_style.dart'; -import 'create_control.dart'; +import '../utils/numbers.dart'; +import '../utils/theme.dart'; +import 'base_controls.dart'; class CupertinoAppBarControl extends StatelessWidget implements ObstructingPreferredSizeWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final bool? parentAdaptive; - final List children; - const CupertinoAppBarControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive}); + const CupertinoAppBarControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("CupertinoAppBar build: ${control.id}"); - var large = control.attrBool("large", false)!; - var leadingCtrls = - children.where((c) => c.name == "leading" && c.isVisible); + // "title" if coming from material AppBar + var middle = control.buildTextOrWidget("middle") ?? + control.buildTextOrWidget("title"); - // "middle" is deprecated in v0.27.0 and will be removed in v0.30.0, use "title" instead - var titleCtrls = children - .where((c) => (c.name == "title" || c.name == "middle") && c.isVisible); + // "actions" if coming from material AppBar + var trailing = + control.buildWidget("trailing") ?? control.buildWidgets("actions"); - // if the material AppBar was used with adaptive=True, AppBar.actions[0] will be used as trailing control - var trailingCtrls = children.where( - (c) => (c.name == "trailing" || c.name == "action") && c.isVisible); - - var leading = leadingCtrls.isNotEmpty - ? createControl(control, leadingCtrls.first.id, control.isDisabled, - parentAdaptive: parentAdaptive) - : null; - - var automaticallyImplyLeading = - control.attrBool("automaticallyImplyLeading", true)!; - var automaticallyImplyTitle = - control.attrBool("automaticallyImplyTitle", control.attrBool("automaticallyImplyMiddle", true)!)!; - var transitionBetweenRoutes = - control.attrBool("transitionBetweenRoutes", true)!; - var border = parseBorder(Theme.of(context), control, "border"); - var previousPageTitle = control.attrString("previousPageTitle"); - var padding = parseEdgeInsetsDirectional(control, "padding"); - var backgroundColor = control.attrColor("bgcolor", context); - var automaticBackgroundVisibility = - control.attrBool("automaticBackgroundVisibility", true)!; - var enableBackgroundFilterBlur = - control.attrBool("backgroundFilterBlur", true)!; - var brightness = parseBrightness(control.attrString("brightness")); - var title = titleCtrls.isNotEmpty - ? createControl(control, titleCtrls.first.id, control.isDisabled, - parentAdaptive: parentAdaptive) - : null; - var trailing = trailingCtrls.length == 1 - ? createControl(control, trailingCtrls.first.id, control.isDisabled, - parentAdaptive: parentAdaptive) - : trailingCtrls.length > 1 - ? Row( - mainAxisSize: MainAxisSize.min, - children: trailingCtrls - .map((c) => createControl(control, c.id, control.isDisabled, - parentAdaptive: parentAdaptive)) - .toList(), - ) - : null; - - var bar = large - ? CupertinoNavigationBar.large( - leading: leading, - automaticallyImplyLeading: automaticallyImplyLeading, - transitionBetweenRoutes: transitionBetweenRoutes, - border: border, - previousPageTitle: previousPageTitle, - padding: padding, - backgroundColor: backgroundColor, - automaticBackgroundVisibility: automaticBackgroundVisibility, - enableBackgroundFilterBlur: enableBackgroundFilterBlur, - brightness: brightness, - trailing: trailing, - largeTitle: title, - automaticallyImplyTitle: automaticallyImplyTitle, - ) - : CupertinoNavigationBar( - leading: leading, - automaticallyImplyLeading: automaticallyImplyLeading, - automaticallyImplyMiddle: automaticallyImplyTitle, - transitionBetweenRoutes: transitionBetweenRoutes, - border: border, - previousPageTitle: previousPageTitle, - padding: padding, - backgroundColor: backgroundColor, - automaticBackgroundVisibility: automaticBackgroundVisibility, - enableBackgroundFilterBlur: enableBackgroundFilterBlur, - brightness: brightness, - middle: title, - trailing: trailing, - ); - return baseControl(context, bar, parent, control); + var bar = CupertinoNavigationBar( + leading: control.buildWidget("leading"), + automaticallyImplyLeading: + control.getBool("automatically_imply_leading", true)!, + automaticallyImplyMiddle: + control.getBool("automatically_imply_middle", true)!, + transitionBetweenRoutes: + control.getBool("transition_between_routes", true)!, + border: control.getBorder("border", Theme.of(context)), + previousPageTitle: control.getString("previous_page_title"), + padding: control.getEdgeInsetsDirectional("padding"), + backgroundColor: control.getColor("bgcolor", context), + automaticBackgroundVisibility: + control.getBool("automatic_background_visibility", true)!, + enableBackgroundFilterBlur: + control.getBool("background_filter_blur", true)!, + brightness: control.getBrightness("brightness"), + middle: middle, + trailing: trailing is Widget + ? trailing + : trailing is List + ? Row( + mainAxisSize: MainAxisSize.min, + children: trailing, + ) + : null); + return BaseControl(control: control, child: bar); } @override @@ -118,7 +65,7 @@ class CupertinoAppBarControl extends StatelessWidget @override bool shouldFullyObstruct(BuildContext context) { final Color backgroundColor = CupertinoDynamicColor.maybeResolve( - control.attrColor("bgcolor", context), context) ?? + control.getColor("bgcolor", context), context) ?? CupertinoTheme.of(context).barBackgroundColor; return backgroundColor.alpha == 0xFF; } diff --git a/packages/flet/lib/src/controls/cupertino_bottom_sheet.dart b/packages/flet/lib/src/controls/cupertino_bottom_sheet.dart index c4dade1ab..3b55eff68 100644 --- a/packages/flet/lib/src/controls/cupertino_bottom_sheet.dart +++ b/packages/flet/lib/src/controls/cupertino_bottom_sheet.dart @@ -1,120 +1,84 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../controls/control_widget.dart'; import '../models/control.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; -class CupertinoBottomSheetControl extends StatefulWidget { - final Control? parent; +class CupertinoBottomSheetControl extends StatelessWidget { final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final Widget? nextChild; - final FletControlBackend backend; - const CupertinoBottomSheetControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentAdaptive, - required this.parentDisabled, - required this.nextChild, - required this.backend}); + const CupertinoBottomSheetControl({ + super.key, + required this.control, + }); - @override - State createState() => - _CupertinoBottomSheetControlState(); -} + Widget _createDialog(BuildContext context) { + Control? content = control.child("content"); -class _CupertinoBottomSheetControlState - extends State { - Widget _createDialog() { - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); + if (content == null) { + return const ErrorControl("CupertinoButtomSheet.content is empty."); + } - Widget content = contentCtrls.isNotEmpty - ? createControl(widget.control, contentCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : const SizedBox.shrink(); + Widget child = ControlWidget(control: content); - if (contentCtrls.isNotEmpty && - ["cupertinopicker", "cupertinotimerpicker", "cupertinodatepicker"] - .contains(contentCtrls.first.type)) { - content = Container( - height: widget.control.attrDouble("height", 220.0)!, - padding: parseEdgeInsets(widget.control, "padding"), + if (["CupertinoPicker", "CupertinoTimerPicker", "CupertinoDatePicker"] + .contains(content.type)) { + child = Container( + height: control.getDouble("height", 220.0)!, + padding: control.getPadding("padding"), // bottom margin is provided to align the popup above the system navigation bar margin: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), // popup background color - color: widget.control.attrColor("bgcolor", context, + color: control.getColor("bgcolor", context, CupertinoColors.systemBackground.resolveFrom(context))!, // Use SafeArea to avoid system overlaps child: SafeArea( top: false, - child: content, + child: child, ), ); } - return Material(child: content); + return Material(child: child); } @override Widget build(BuildContext context) { - debugPrint("CupertinoBottomSheet build: ${widget.control.id}"); + debugPrint("CupertinoBottomSheet build: ${control.id}"); - bool lastOpen = widget.control.state["open"] ?? false; + bool lastOpen = control.getBool("_open", false)!; - var open = widget.control.attrBool("open", false)!; - var modal = widget.control.attrBool("modal", false)!; - - debugPrint("Current open state: $lastOpen"); - debugPrint("New open state: $open"); + var open = control.getBool("open", false)!; if (open && (open != lastOpen)) { - var dialog = _createDialog(); + var dialog = _createDialog(context); if (dialog is ErrorControl) { return dialog; } - // close previous dialog - if (ModalRoute.of(context)?.isCurrent != true) { - Navigator.of(context).pop(); - } - - widget.control.state["open"] = open; + control.updateProperties({"_open": open}, python: false); WidgetsBinding.instance.addPostFrameCallback((_) { showCupertinoModalPopup( - barrierDismissible: !modal, + barrierDismissible: !control.getBool("modal", false)!, useRootNavigator: false, context: context, - builder: (context) => _createDialog()).then((value) { - lastOpen = widget.control.state["open"] ?? false; - debugPrint( - "CupertinoBottomSheet should be dismissed ($hashCode): $lastOpen"); - bool shouldDismiss = lastOpen; - widget.control.state["open"] = false; - - if (shouldDismiss) { - widget.backend - .updateControlState(widget.control.id, {"open": "false"}); - widget.backend.triggerControlEvent(widget.control.id, "dismiss"); - } + builder: (context) => dialog).then((value) { + debugPrint("Dismiss CupertinoBottomSheet: $lastOpen"); + control.updateProperties({"_open": false}, python: false); + control.updateProperties({"open": false}); + control.triggerEvent("dismiss"); }); }); - } else if (open != lastOpen && lastOpen) { + } else if (open != lastOpen && lastOpen && Navigator.of(context).canPop()) { Navigator.of(context).pop(); } - return widget.nextChild ?? const SizedBox.shrink(); + return const SizedBox.shrink(); } } diff --git a/packages/flet/lib/src/controls/cupertino_button.dart b/packages/flet/lib/src/controls/cupertino_button.dart index bf1d6d530..2d52ae9ed 100644 --- a/packages/flet/lib/src/controls/cupertino_button.dart +++ b/packages/flet/lib/src/controls/cupertino_button.dart @@ -1,188 +1,210 @@ import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/alignment.dart'; import '../utils/borders.dart'; import '../utils/buttons.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; -import '../utils/icons.dart'; import '../utils/launch_url.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; class CupertinoButtonControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - - const CupertinoButtonControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + + CupertinoButtonControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _CupertinoButtonControlState(); } class _CupertinoButtonControlState extends State { + late final FocusNode _focusNode; + + @override + void initState() { + super.initState(); + _focusNode = FocusNode(); + _focusNode.addListener(_onFocusChange); + widget.control.addInvokeMethodListener(_invokeMethod); + } + + @override + void dispose() { + _focusNode.removeListener(_onFocusChange); + _focusNode.dispose(); + widget.control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } + + void _onFocusChange() { + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); + } + + Future _invokeMethod(String name, dynamic args) async { + debugPrint("CupertinoButton.$name($args)"); + switch (name) { + case "focus": + _focusNode.requestFocus(); + default: + throw Exception("Unknown CupertinoButton method: $name"); + } + } + @override Widget build(BuildContext context) { debugPrint("CupertinoButton build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - var theme = Theme.of(context); - - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); - - String? text = widget.control.attrString("text"); - IconData? icon = parseIcon(widget.control.attrString("icon")); - Color? iconColor = widget.control.attrColor("iconColor", context); - - // IconButton props below - double? iconSize = widget.control.attrDouble("iconSize"); - bool selected = widget.control.attrBool("selected", false)!; - IconData? selectedIcon = - parseIcon(widget.control.attrString("selectedIcon")); - Color? selectedIconColor = - widget.control.attrColor("selectedIconColor", context); - - Widget? content; - List children = []; - if (icon != null) { - children.add(Icon( - selected ? selectedIcon : icon, - color: selected - ? selectedIconColor - : disabled - ? theme.disabledColor - : iconColor, - size: iconSize, - )); - } - if (text != null) { - children.add(Text(text)); - } + Color? iconColor = widget.control.getColor("icon_color", context); - if (contentCtrls.isNotEmpty) { - content = createControl(widget.control, contentCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive); - } else if (children.isNotEmpty) { - if (children.length == 2) { - children.insert(1, const SizedBox(width: 8)); - content = Row( + Widget? icon = widget.control.buildIconOrWidget("icon", color: iconColor); + Widget? content = widget.control.buildTextOrWidget("content"); + + Widget child; + if (icon != null) { + if (content != null) { + child = Row( mainAxisSize: MainAxisSize.min, - children: children, + children: [icon, const SizedBox(width: 8), content], ); } else { - content = children.first; + child = icon; } + } else { + child = content ?? const Text(""); } - if (content == null) { - return const ErrorControl( - "CupertinoButton has nothing to display", - description: "Provide at minimum text or (visible) content", - ); - } - - double pressedOpacity = widget.control.attrDouble("opacityOnClick", 0.4)!; - double minSize = widget.control.attrDouble("minSize", 44.0)!; - String url = widget.control.attrString("url", "")!; - Color disabledColor = - widget.control.attrColor("disabledBgcolor", context) ?? - CupertinoColors.quaternarySystemFill; - Color? bgColor = widget.control.attrColor("bgColor", context); - Color? color = widget.control.attrColor("color", context); - AlignmentGeometry alignment = - parseAlignment(widget.control, "alignment", Alignment.center)!; - BorderRadius borderRadius = parseBorderRadius(widget.control, - "borderRadius", const BorderRadius.all(Radius.circular(8.0)))!; - - EdgeInsets? padding = parseEdgeInsets(widget.control, "padding"); - - var style = parseButtonStyle(Theme.of(context), widget.control, "style", - defaultForegroundColor: theme.colorScheme.primary, - defaultBackgroundColor: Colors.transparent, - defaultOverlayColor: Colors.transparent, - defaultShadowColor: Colors.transparent, - defaultSurfaceTintColor: Colors.transparent, - defaultElevation: 0, - defaultPadding: const EdgeInsets.all(8), - defaultBorderSide: BorderSide.none, - defaultShape: theme.useMaterial3 - ? const StadiumBorder() - : RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))); - - if (style != null) { - Set widgetStates = selected ? {WidgetState.selected} : {}; - - // Check if the widget is disabled and update the foregroundColor accordingly - // backgroundColor is not updated here, as it is handled by disabledColor - if (disabled) { - style = style.copyWith( - foregroundColor: WidgetStatePropertyAll(theme.disabledColor), - ); - } - - // Resolve color, background color, and padding based on widget states - color = style.foregroundColor?.resolve(widgetStates); - bgColor = style.backgroundColor?.resolve(widgetStates); - padding = style.padding?.resolve({}) as EdgeInsets?; - } - + double pressedOpacity = widget.control.getDouble("opacity_on_click", 0.4)!; + double? minSize = widget.control.getDouble("min_size"); + bool autofocus = widget.control.getBool("autofocus", false)!; + Color? bgColor = widget.control.getColor("bgcolor", context); + Color? focusColor = widget.control.getColor("focus_color", context); + + CupertinoButtonSize sizeStyle = widget.control + .getCupertinoButtonSize("size_style", CupertinoButtonSize.large)!; + + var alignment = widget.control.getAlignment("alignment", Alignment.center)!; + var borderRadius = widget.control.getBorderRadius( + "border_radius", const BorderRadius.all(Radius.circular(8.0)))!; + + var padding = widget.control.getPadding("padding"); + bool isFilledButton = + {"CupertinoFilledButton", "FilledButton"}.contains(widget.control.type); + bool isTintedButton = {"CupertinoTintedButton", "FilledTonalButton"} + .contains(widget.control.type); + + // var style = widget.control.getButtonStyle("style", Theme.of(context), + // defaultForegroundColor: theme.colorScheme.primary, + // defaultBackgroundColor: Colors.transparent, + // defaultOverlayColor: Colors.transparent, + // defaultShadowColor: Colors.transparent, + // defaultSurfaceTintColor: Colors.transparent, + // defaultElevation: 0, + // defaultPadding: const EdgeInsets.all(8), + // defaultBorderSide: BorderSide.none, + // defaultShape: theme.useMaterial3 + // ? const StadiumBorder() + // : RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))); + + // if (style != null) { + // Set widgetStates = selected ? {WidgetState.selected} : {}; + + // // Check if the widget is disabled and update the foregroundColor accordingly + // // backgroundColor is not updated here, as it is handled by disabledColor + // if (control.disabled) { + // style = style.copyWith( + // foregroundColor: WidgetStatePropertyAll(theme.disabledColor), + // ); + // } + + // // Resolve color, background color, and padding based on widget states + // color = style.foregroundColor?.resolve(widgetStates); + // bgColor = style.backgroundColor?.resolve(widgetStates); + // padding = style.padding?.resolve({}) as EdgeInsets?; + // } + var color = widget.control.getColor("color", context); + var disabledColor = widget.control.getColor( + "disabled_bgcolor", context, CupertinoColors.tertiarySystemFill)!; if (color != null) { - content = DefaultTextStyle( + child = DefaultTextStyle( style: CupertinoTheme.of(context) .textTheme .textStyle .copyWith(color: color), - child: content); + child: child); } - - Function()? onPressed = !disabled + var url = widget.control.getString("url"); + Function()? onPressed = !widget.control.disabled ? () { - debugPrint("CupertinoButton ${widget.control.id} clicked!"); - if (url != "") { + if (url != null) { openWebBrowser(url, - webWindowName: widget.control.attrString("urlTarget")); + webWindowName: widget.control.getString("url_target")); } - widget.backend.triggerControlEvent(widget.control.id, "click"); + widget.control.triggerEvent("click"); + } + : null; + Function()? onLongPressed = !widget.control.disabled + ? () { + widget.control.triggerEvent("long_press"); } : null; - CupertinoButton? button = CupertinoButton( - onPressed: onPressed, - disabledColor: disabledColor, - color: bgColor, - padding: padding, - borderRadius: borderRadius, - pressedOpacity: pressedOpacity, - alignment: alignment, - minSize: minSize, - autofocus: widget.control.attrBool("autofocus", false)!, - focusColor: widget.control.attrColor("focusColor", context), - onLongPress: !disabled - ? () { - widget.backend - .triggerControlEvent(widget.control.id, "longPress"); - } - : null, - onFocusChange: (focused) { - widget.backend - .triggerControlEvent(widget.control.id, focused ? "focus" : "blur"); - }, - child: content, - ); - - return constrainedControl(context, button, widget.parent, widget.control); + CupertinoButton? button; + if (isFilledButton) { + button = CupertinoButton.filled( + onPressed: onPressed, + disabledColor: disabledColor, + //color: widget.control.getColor("bgcolor", context), + padding: padding, + borderRadius: borderRadius, + pressedOpacity: pressedOpacity, + alignment: alignment, + minSize: minSize, + sizeStyle: sizeStyle, + autofocus: autofocus, + focusColor: focusColor, + onLongPress: onLongPressed, + focusNode: _focusNode, + child: child, + ); + } else if (isTintedButton) { + button = CupertinoButton.tinted( + onPressed: onPressed, + disabledColor: disabledColor, + color: bgColor, + padding: padding, + borderRadius: borderRadius, + pressedOpacity: pressedOpacity, + alignment: alignment, + minSize: minSize, + sizeStyle: sizeStyle, + autofocus: autofocus, + focusColor: focusColor, + onLongPress: onLongPressed, + focusNode: _focusNode, + child: child, + ); + } else { + button = CupertinoButton( + onPressed: onPressed, + disabledColor: disabledColor, + color: bgColor, + padding: padding, + borderRadius: borderRadius, + pressedOpacity: pressedOpacity, + alignment: alignment, + minSize: minSize, + sizeStyle: sizeStyle, + autofocus: autofocus, + focusColor: focusColor, + onLongPress: onLongPressed, + focusNode: _focusNode, + child: child, + ); + } + + return ConstrainedControl(control: widget.control, child: button); } } diff --git a/packages/flet/lib/src/controls/cupertino_checkbox.dart b/packages/flet/lib/src/controls/cupertino_checkbox.dart index 135d83164..545beef0b 100644 --- a/packages/flet/lib/src/controls/cupertino_checkbox.dart +++ b/packages/flet/lib/src/controls/cupertino_checkbox.dart @@ -1,27 +1,22 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/borders.dart'; import '../utils/colors.dart'; +import '../utils/misc.dart'; import '../utils/mouse.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; +import '../utils/numbers.dart'; +import '../utils/text.dart'; +import 'base_controls.dart'; import 'list_tile.dart'; class CupertinoCheckboxControl extends StatefulWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final FletControlBackend backend; - const CupertinoCheckboxControl( - {super.key, - this.parent, - required this.control, - required this.parentDisabled, - required this.backend}); + CupertinoCheckboxControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _CheckboxControlState(); @@ -40,8 +35,7 @@ class _CheckboxControlState extends State { } void _onFocusChange() { - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); } @override @@ -64,68 +58,79 @@ class _CheckboxControlState extends State { } void _onChange(bool? value) { - var svalue = value != null ? value.toString() : ""; _value = value; - widget.backend.updateControlState(widget.control.id, {"value": svalue}); - widget.backend.triggerControlEvent(widget.control.id, "change", svalue); + widget.control.updateProperties({"value": value}, notify: true); + widget.control.triggerEvent("change", value); } @override Widget build(BuildContext context) { debugPrint("CupertinoCheckBox build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - String label = widget.control.attrString("label", "")!; - LabelPosition labelPosition = parseLabelPosition( - widget.control.attrString("labelPosition"), LabelPosition.right)!; - _tristate = widget.control.attrBool("tristate", false)!; - bool autofocus = widget.control.attrBool("autofocus", false)!; - - bool? value = widget.control.attrBool("value", _tristate ? null : false); + _tristate = widget.control.getBool("tristate", false)!; + var value = widget.control.getBool("value", _tristate ? null : false); if (_value != value) { _value = value; } var cupertinoCheckbox = CupertinoCheckbox( - autofocus: autofocus, + autofocus: widget.control.getBool("autofocus", false)!, focusNode: _focusNode, value: _value, - activeColor: parseColor(Theme.of(context), - widget.control.attrString("activeColor", "primary")!), - checkColor: widget.control.attrColor("checkColor", context), - focusColor: widget.control.attrColor("focusColor", context), - shape: parseOutlinedBorder(widget.control, "shape"), - mouseCursor: parseMouseCursor(widget.control.attrString("mouseCursor")), - semanticLabel: widget.control.attrString("semanticsLabel"), - side: parseWidgetStateBorderSide( - Theme.of(context), widget.control, "borderSide"), - fillColor: parseWidgetStateColor( - Theme.of(context), widget.control, "fillColor"), + activeColor: widget.control.getColor( + "active_color", context, Theme.of(context).colorScheme.primary)!, + checkColor: widget.control.getColor("check_color", context), + focusColor: widget.control.getColor("focus_color", context), + shape: widget.control.getShape("shape", Theme.of(context)), + mouseCursor: widget.control.getMouseCursor("mouse_cursor"), + semanticLabel: widget.control.getString("semantics_label"), + side: widget.control + .getWidgetStateBorderSide("border_side", Theme.of(context)), + fillColor: + widget.control.getWidgetStateColor("fill_color", Theme.of(context)), tristate: _tristate, - onChanged: !disabled - ? (bool? value) { - _onChange(value); - } + onChanged: !widget.control.disabled + ? (bool? value) => _onChange(value) : null); + // Add listener to ListTile clicks ListTileClicks.of(context)?.notifier.addListener(() { _toggleValue(); }); Widget result = cupertinoCheckbox; - if (label != "") { - var labelWidget = disabled - ? Text(label, - style: TextStyle(color: Theme.of(context).disabledColor)) - : MouseRegion(cursor: SystemMouseCursors.click, child: Text(label)); + + var labelStyle = + widget.control.getTextStyle("label_style", Theme.of(context)); + if (widget.control.disabled && labelStyle != null) { + labelStyle = labelStyle.apply(color: Theme.of(context).disabledColor); + } + var label = + widget.control.buildTextOrWidget("label", textStyle: labelStyle); + if (label != null) { + label = widget.control.disabled + ? label + : MouseRegion(cursor: SystemMouseCursors.click, child: label); + var labelPosition = widget.control + .getLabelPosition("label_position", LabelPosition.right)!; result = MergeSemantics( child: GestureDetector( - onTap: !disabled ? _toggleValue : null, + onTap: !widget.control.disabled ? _toggleValue : null, child: labelPosition == LabelPosition.right - ? Row(children: [cupertinoCheckbox, labelWidget]) - : Row(children: [labelWidget, cupertinoCheckbox]))); + ? Row(children: [cupertinoCheckbox, label]) + : Row(children: [label, cupertinoCheckbox]))); + } + + // Apply width and height if provided + var width = widget.control.getDouble("width"); + var height = widget.control.getDouble("height"); + if (width != null || height != null) { + result = SizedBox( + width: width, + height: height, + child: FittedBox(fit: BoxFit.fill, child: result)); } - return constrainedControl(context, result, widget.parent, widget.control); + return ConstrainedControl(control: widget.control, child: result); } } diff --git a/packages/flet/lib/src/controls/cupertino_context_menu.dart b/packages/flet/lib/src/controls/cupertino_context_menu.dart index 1cee81748..c5d938114 100644 --- a/packages/flet/lib/src/controls/cupertino_context_menu.dart +++ b/packages/flet/lib/src/controls/cupertino_context_menu.dart @@ -1,27 +1,16 @@ import 'package:flutter/cupertino.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import 'create_control.dart'; -import 'error.dart'; -import 'flet_store_mixin.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; +import '../widgets/flet_store_mixin.dart'; class CupertinoContextMenuControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const CupertinoContextMenuControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + CupertinoContextMenuControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => @@ -34,32 +23,22 @@ class _CupertinoContextMenuControlState Widget build(BuildContext context) { debugPrint("CupertinoContextMenu build ($hashCode): ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); - var actionCtrls = - widget.children.where((c) => c.name == "action" && c.isVisible); + var content = widget.control.buildWidget("content"); + var actions = widget.control.buildWidgets("actions"); - if (actionCtrls.isEmpty) { + if (actions.isEmpty) { return const ErrorControl( - "CupertinoContextMenu.actions must be provided and at least one action must be visible"); + "at least one action in CupertinoContextMenu.actions must be visible"); } - if (contentCtrls.isEmpty) { - return const ErrorControl( - "CupertinoContextMenu.content must be provided and visible"); + if (content == null) { + return const ErrorControl("CupertinoContextMenu.content must be visible"); } return CupertinoContextMenu( enableHapticFeedback: - widget.control.attrBool("enableHapticFeedback", false)!, - actions: actionCtrls.map((c) { - return createControl(widget.control, c.id, disabled, - parentAdaptive: adaptive); - }).toList(), - child: createControl(widget.control, contentCtrls.first.id, disabled, - parentAdaptive: adaptive), + widget.control.getBool("enable_haptic_feedback", false)!, + actions: actions, + child: content, ); } } diff --git a/packages/flet/lib/src/controls/cupertino_context_menu_action.dart b/packages/flet/lib/src/controls/cupertino_context_menu_action.dart index e622ed8a7..491c3d691 100644 --- a/packages/flet/lib/src/controls/cupertino_context_menu_action.dart +++ b/packages/flet/lib/src/controls/cupertino_context_menu_action.dart @@ -1,64 +1,34 @@ import 'package:flutter/cupertino.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/icons.dart'; -import 'create_control.dart'; -import 'flet_store_mixin.dart'; +import '../utils/numbers.dart'; -class CupertinoContextMenuActionControl extends StatefulWidget { - final Control? parent; +class CupertinoContextMenuActionControl extends StatelessWidget { final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const CupertinoContextMenuActionControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const CupertinoContextMenuActionControl({super.key, required this.control}); @override - State createState() => - _CupertinoContextMenuActionControlState(); -} - -class _CupertinoContextMenuActionControlState - extends State with FletStoreMixin { - @override Widget build(BuildContext context) { - debugPrint( - "CupertinoContextMenuAction build ($hashCode): ${widget.control.id}"); - - bool disabled = widget.control.isDisabled || widget.parentDisabled; - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - String text = widget.control.attrString("text", "")!; - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); - IconData? trailingIcon = - parseIcon(widget.control.attrString("trailingIcon")); - + debugPrint("CupertinoContextMenuAction build ($hashCode): ${control.id}"); + var content = control.buildTextOrWidget("content", + textStyle: const TextStyle(overflow: TextOverflow.ellipsis)); + if (content == null) { + return ErrorWidget( + "content (string or visible Control) must be provided"); + } return CupertinoContextMenuAction( - isDefaultAction: widget.control.attrBool("isDefaultAction", false)!, - isDestructiveAction: - widget.control.attrBool("isDestructiveAction", false)!, - onPressed: () { - if (!disabled) { - widget.backend.triggerControlEvent(widget.control.id, "click"); - Navigator.of(context).pop(); - } - }, - trailingIcon: trailingIcon, - child: contentCtrls.isNotEmpty - ? createControl(widget.control, contentCtrls.first.id, disabled, - parentAdaptive: adaptive) - : Text(text, overflow: TextOverflow.ellipsis), - ); + isDefaultAction: control.getBool("default", false)!, + isDestructiveAction: control.getBool("destructive", false)!, + onPressed: () { + if (!control.disabled) { + control.triggerEvent("click"); + Navigator.of(context).pop(); // Close the context menu + } + }, + trailingIcon: control.getIcon("trailing_icon"), + child: content); } } diff --git a/packages/flet/lib/src/controls/cupertino_date_picker.dart b/packages/flet/lib/src/controls/cupertino_date_picker.dart index c3980ddbc..9d038c014 100644 --- a/packages/flet/lib/src/controls/cupertino_date_picker.dart +++ b/packages/flet/lib/src/controls/cupertino_date_picker.dart @@ -1,23 +1,17 @@ import 'package:flutter/cupertino.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/colors.dart'; +import '../utils/numbers.dart'; +import '../utils/time.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; class CupertinoDatePickerControl extends StatefulWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final FletControlBackend backend; - const CupertinoDatePickerControl( - {super.key, - this.parent, - required this.control, - required this.parentDisabled, - required this.backend}); + CupertinoDatePickerControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => @@ -26,8 +20,6 @@ class CupertinoDatePickerControl extends StatefulWidget { class _CupertinoDatePickerControlState extends State { - static const double _kItemExtent = 32.0; - @override Widget build(BuildContext context) { debugPrint("CupertinoDatePicker build: ${widget.control.id}"); @@ -35,33 +27,28 @@ class _CupertinoDatePickerControlState Widget dialog; try { dialog = CupertinoDatePicker( - initialDateTime: widget.control.attrDateTime("value"), - showDayOfWeek: widget.control.attrBool("showDayOfWeek", false)!, - minimumDate: widget.control.attrDateTime("firstDate"), - maximumDate: widget.control.attrDateTime("lastDate"), - backgroundColor: widget.control.attrColor("bgcolor", context), - minimumYear: widget.control.attrInt("minimumYear", 1)!, - maximumYear: widget.control.attrInt("maximumYear"), - itemExtent: widget.control.attrDouble("itemExtent", _kItemExtent)!, - minuteInterval: widget.control.attrInt("minuteInterval", 1)!, - use24hFormat: widget.control.attrBool("use24hFormat", false)!, - dateOrder: - parseDatePickerDateOrder(widget.control.attrString("dateOrder")), - mode: parseCupertinoDatePickerMode( - widget.control.attrString("datePickerMode"), - CupertinoDatePickerMode.dateAndTime)!, + initialDateTime: widget.control.getDateTime("value"), + showDayOfWeek: widget.control.getBool("show_day_of_week", false)!, + minimumDate: widget.control.getDateTime("first_date"), + maximumDate: widget.control.getDateTime("last_date"), + backgroundColor: widget.control.getColor("bgcolor", context), + minimumYear: widget.control.getInt("minimum_year", 1)!, + maximumYear: widget.control.getInt("maximum_year"), + itemExtent: widget.control.getDouble("item_extent", 32.0)!, + minuteInterval: widget.control.getInt("minute_interval", 1)!, + use24hFormat: widget.control.getBool("use_24h_format", false)!, + dateOrder: widget.control.getDatePickerDateOrder("date_order"), + mode: widget.control.getCupertinoDatePickerMode( + "date_picker_mode", CupertinoDatePickerMode.dateAndTime)!, onDateTimeChanged: (DateTime value) { - String stringValue = value.toIso8601String(); - widget.backend - .updateControlState(widget.control.id, {"value": stringValue}); - widget.backend - .triggerControlEvent(widget.control.id, "change", stringValue); + widget.control.updateProperties({"value": value}); + widget.control.triggerEvent("change", value); }, ); } catch (e) { return ErrorControl("CupertinoDatePicker Error: ${e.toString()}"); } - return constrainedControl(context, dialog, widget.parent, widget.control); + return ConstrainedControl(control: widget.control, child: dialog); } } diff --git a/packages/flet/lib/src/controls/cupertino_dialog_action.dart b/packages/flet/lib/src/controls/cupertino_dialog_action.dart index 4ce3c0906..9a37f8417 100644 --- a/packages/flet/lib/src/controls/cupertino_dialog_action.dart +++ b/packages/flet/lib/src/controls/cupertino_dialog_action.dart @@ -1,51 +1,36 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; +import '../utils/numbers.dart'; import '../utils/text.dart'; -import 'create_control.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; class CupertinoDialogActionControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const CupertinoDialogActionControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const CupertinoDialogActionControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("CupertinoDialogAction build: ${control.id}"); - bool disabled = control.isDisabled || parentDisabled; - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); + var content = control.buildTextOrWidget("content"); + if (content == null) { + return const ErrorControl( + "CupertinoDialogAction.content must be a string or visible Control"); + } var cupertinoDialogAction = CupertinoDialogAction( - isDefaultAction: control.attrBool("isDefaultAction", false)!, - isDestructiveAction: control.attrBool("isDestructiveAction", false)!, - textStyle: parseTextStyle(Theme.of(context), control, "textStyle"), - onPressed: !disabled - ? () { - debugPrint("CupertinoDialogAction ${control.id} clicked!"); - backend.triggerControlEvent(control.id, "click"); - } - : null, - child: contentCtrls.isNotEmpty - ? createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: parentAdaptive) - : Text(control.attrString("text", "")!)); + isDefaultAction: control.getBool("default", false)!, + isDestructiveAction: control.getBool("destructive", false)!, + textStyle: control.getTextStyle("text_style", Theme.of(context)), + onPressed: !control.disabled ? () => control.triggerEvent("click") : null, + child: content, + ); - return baseControl(context, cupertinoDialogAction, parent, control); + return BaseControl(control: control, child: cupertinoDialogAction); } } diff --git a/packages/flet/lib/src/controls/cupertino_list_tile.dart b/packages/flet/lib/src/controls/cupertino_list_tile.dart index c04a3dd58..dd82e459f 100644 --- a/packages/flet/lib/src/controls/cupertino_list_tile.dart +++ b/packages/flet/lib/src/controls/cupertino_list_tile.dart @@ -1,120 +1,84 @@ import 'package:flutter/cupertino.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; import '../utils/launch_url.dart'; -import 'create_control.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; import 'list_tile.dart'; class CupertinoListTileControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; final ListTileClickNotifier _clickNotifier = ListTileClickNotifier(); - CupertinoListTileControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + CupertinoListTileControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("CupertinoListTile build: ${control.id}"); - var leadingCtrls = - children.where((c) => c.name == "leading" && c.isVisible); - var titleCtrls = children.where((c) => c.name == "title" && c.isVisible); - var subtitleCtrls = - children.where((c) => c.name == "subtitle" && c.isVisible); - var trailingCtrls = - children.where((c) => c.name == "trailing" && c.isVisible); - var additionalInfoCtrls = - children.where((c) => c.name == "additionalInfo" && c.isVisible); - - bool notched = control.attrBool("notched", false)!; - bool onclick = control.attrBool("onclick", false)!; - bool toggleInputs = control.attrBool("toggleInputs", false)!; - String url = control.attrString("url", "")!; - String? urlTarget = control.attrString("urlTarget"); - bool disabled = control.isDisabled || parentDisabled; - - Widget? additionalInfo = additionalInfoCtrls.isNotEmpty - ? createControl(control, additionalInfoCtrls.first.id, disabled, - parentAdaptive: parentAdaptive) - : null; - Widget? leading = leadingCtrls.isNotEmpty - ? createControl(control, leadingCtrls.first.id, disabled, - parentAdaptive: parentAdaptive) - : null; - - Widget? title = titleCtrls.isNotEmpty - ? createControl(control, titleCtrls.first.id, disabled, - parentAdaptive: parentAdaptive) - : const Text(""); - - Widget? subtitle = subtitleCtrls.isNotEmpty - ? createControl(control, subtitleCtrls.first.id, disabled, - parentAdaptive: parentAdaptive) - : null; - - Widget? trailing = trailingCtrls.isNotEmpty - ? createControl(control, trailingCtrls.first.id, disabled, - parentAdaptive: parentAdaptive) - : null; - - Color? backgroundColor = control.attrColor("bgcolor", context); - Color? bgcolorActivated = control.attrColor("bgcolorActivated", context); - - var padding = parseEdgeInsets(control, "contentPadding"); - var leadingSize = control.attrDouble("leadingSize"); - var leadingToTitle = control.attrDouble("leadingToTitle"); - - Function()? onPressed = (onclick || toggleInputs || url != "") && !disabled - ? () { - debugPrint("CupertinoListTile ${control.id} clicked!"); - if (toggleInputs) { - _clickNotifier.onClick(); - } - if (url != "") { - openWebBrowser(url, webWindowName: urlTarget); - } - if (onclick) { - backend.triggerControlEvent(control.id, "click"); - } - } - : null; + var title = control.buildTextOrWidget("title"); + if (title == null) { + return const ErrorControl( + "CupertinoListTile.title must be provided and visible"); + } + var leading = control.buildIconOrWidget("leading"); + var additionalInfo = control.buildTextOrWidget("additional_info"); + var subtitle = control.buildTextOrWidget("subtitle"); + var trailing = control.buildIconOrWidget("trailing"); + var backgroundColor = control.getColor("bgcolor", context); + var bgcolorActivated = control.getColor("bgcolor_activated", context); + var padding = control.getPadding("content_padding"); + var notched = control.getBool("notched", false)!; + var leadingSize = control.getDouble("leading_size", notched ? 30.0 : 28.0)!; + var leadingToTitle = + control.getDouble("leading_to_title", notched ? 12.0 : 16.0)!; + var onclick = control.getBool("on_click", false)!; + var toggleInputs = control.getBool("toggle_inputs", false)!; + var url = control.getString("url"); + var urlTarget = control.getString("url_target"); + + Function()? onPressed = + (onclick || toggleInputs || url != "") && !control.disabled + ? () { + if (toggleInputs) { + _clickNotifier.onClick(); + } + if (url != null) { + openWebBrowser(url, webWindowName: urlTarget); + } + if (onclick) { + control.triggerEvent("click"); + } + } + : null; Widget tile; - !notched - ? tile = CupertinoListTile( + notched + ? tile = CupertinoListTile.notched( onTap: onPressed, additionalInfo: additionalInfo, backgroundColor: backgroundColor, backgroundColorActivated: bgcolorActivated, leading: leading, - leadingSize: leadingSize ?? 28.0, - leadingToTitle: leadingToTitle ?? 16.0, + leadingSize: leadingSize, + leadingToTitle: leadingToTitle, padding: padding, title: title, subtitle: subtitle, trailing: trailing, ) - : tile = CupertinoListTile.notched( + : tile = CupertinoListTile( onTap: onPressed, additionalInfo: additionalInfo, backgroundColor: backgroundColor, backgroundColorActivated: bgcolorActivated, leading: leading, - leadingSize: leadingSize ?? 30.0, - leadingToTitle: leadingToTitle ?? 12.0, + leadingSize: leadingSize, + leadingToTitle: leadingToTitle, padding: padding, title: title, subtitle: subtitle, @@ -125,6 +89,6 @@ class CupertinoListTileControl extends StatelessWidget { tile = ListTileClicks(notifier: _clickNotifier, child: tile); } - return constrainedControl(context, tile, parent, control); + return ConstrainedControl(control: control, child: tile); } } diff --git a/packages/flet/lib/src/controls/cupertino_navigation_bar.dart b/packages/flet/lib/src/controls/cupertino_navigation_bar.dart index c984a5d33..e749b3300 100644 --- a/packages/flet/lib/src/controls/cupertino_navigation_bar.dart +++ b/packages/flet/lib/src/controls/cupertino_navigation_bar.dart @@ -1,31 +1,20 @@ -import 'dart:convert'; - import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/borders.dart'; +import '../utils/colors.dart'; import '../utils/icons.dart'; -import 'create_control.dart'; -import 'flet_store_mixin.dart'; +import '../utils/numbers.dart'; +import '../widgets/flet_store_mixin.dart'; +import 'base_controls.dart'; class CupertinoNavigationBarControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const CupertinoNavigationBarControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + CupertinoNavigationBarControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => @@ -36,72 +25,42 @@ class _CupertinoNavigationBarControlState extends State with FletStoreMixin { int _selectedIndex = 0; - bool get disabled => widget.control.isDisabled || widget.parentDisabled; - void _onTap(int index) { _selectedIndex = index; - debugPrint("Selected index: $_selectedIndex"); - widget.backend.updateControlState( - widget.control.id, {"selectedindex": _selectedIndex.toString()}); - widget.backend.triggerControlEvent( - widget.control.id, "change", _selectedIndex.toString()); + widget.control + .updateProperties({"selected_index": _selectedIndex}, notify: true); + widget.control.triggerEvent("change", _selectedIndex); } @override Widget build(BuildContext context) { debugPrint("CupertinoNavigationBarControl build: ${widget.control.id}"); - var selectedIndex = widget.control.attrInt("selectedIndex", 0)!; - + var selectedIndex = widget.control.getInt("selected_index", 0)!; if (_selectedIndex != selectedIndex) { _selectedIndex = selectedIndex; } - var navBar = withControls( - widget.children - .where((c) => c.isVisible) - .map((c) => c.id), (content, viewModel) { - return CupertinoTabBar( - backgroundColor: widget.control.attrColor("bgColor", context), - activeColor: widget.control.attrColor("activeColor", context) ?? - widget.control.attrColor("indicatorColor", context), - inactiveColor: widget.control.attrColor("inactiveColor", context) ?? - CupertinoColors.inactiveGray, - iconSize: widget.control.attrDouble("iconSize", 30.0)!, - currentIndex: _selectedIndex, - border: parseBorder(Theme.of(context), widget.control, "border"), - onTap: disabled ? null : _onTap, - items: viewModel.controlViews.map((destView) { - var label = destView.control.attrString("label", "")!; - var iconStr = parseIcon(destView.control.attrString("icon")); - var iconCtrls = - destView.children.where((c) => c.name == "icon" && c.isVisible); - var selectedIconStr = - parseIcon(destView.control.attrString("selectedIcon")); - var selectedIconCtrls = destView.children - .where((c) => c.name == "selected_icon" && c.isVisible); - var destinationDisabled = disabled || destView.control.isDisabled; - var destinationTooltip = destView.control.attrString("tooltip"); - return BottomNavigationBarItem( - tooltip: !destinationDisabled && destinationTooltip != null - ? jsonDecode(destinationTooltip) - : null, - backgroundColor: widget.control.attrColor("bgColor", context), - icon: iconCtrls.isNotEmpty - ? createControl(destView.control, iconCtrls.first.id, - destinationDisabled, - parentAdaptive: widget.parentAdaptive) - : Icon(iconStr), - activeIcon: selectedIconCtrls.isNotEmpty - ? createControl(destView.control, - selectedIconCtrls.first.id, destinationDisabled, - parentAdaptive: widget.parentAdaptive) - : selectedIconStr != null - ? Icon(selectedIconStr) - : null, - label: label); - }).toList()); - }); + var navBar = CupertinoTabBar( + backgroundColor: widget.control.getColor("bgcolor", context), + activeColor: widget.control.getColor("active_color", context) ?? + widget.control.getColor("indicator_color", context), + // "indicator_color" from adaptive Material NavBar + inactiveColor: widget.control + .getColor("inactive_color", context, CupertinoColors.inactiveGray)!, + iconSize: widget.control.getDouble("icon_size", 30.0)!, + currentIndex: _selectedIndex, + border: widget.control.getBorder("border", Theme.of(context)), + onTap: widget.control.disabled ? null : _onTap, + items: widget.control.children("destinations").map((dest) { + var icon = parseIcon(dest.getString("icon")); + return BottomNavigationBarItem( + tooltip: !dest.disabled ? dest.getString("tooltip") : null, + backgroundColor: dest.getColor("bgcolor", context), + icon: dest.buildWidget("icon") ?? Icon(icon), + activeIcon: dest.buildIconOrWidget("selected_icon"), + label: dest.getString("label", "")!); + }).toList()); - return constrainedControl(context, navBar, widget.parent, widget.control); + return ConstrainedControl(control: widget.control, child: navBar); } } diff --git a/packages/flet/lib/src/controls/cupertino_picker.dart b/packages/flet/lib/src/controls/cupertino_picker.dart index 0a4435b53..4ad847bbd 100644 --- a/packages/flet/lib/src/controls/cupertino_picker.dart +++ b/packages/flet/lib/src/controls/cupertino_picker.dart @@ -1,65 +1,30 @@ import 'package:flutter/cupertino.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import 'create_control.dart'; - -const double _kItemExtent = 32.0; -const double _kDefaultDiameterRatio = 1.07; -const double _kSqueeze = 1.45; +import '../utils/colors.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; +import 'control_widget.dart'; class CupertinoPickerControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const CupertinoPickerControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentAdaptive, - required this.parentDisabled, - required this.backend}); + CupertinoPickerControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _CupertinoPickerControlState(); } class _CupertinoPickerControlState extends State { - int _index = 0; - int previousIndex = 0; - bool isScrollUp = false; - bool isScrollDown = true; FixedExtentScrollController scrollController = FixedExtentScrollController(); @override void initState() { super.initState(); scrollController = FixedExtentScrollController( - initialItem: widget.control.attrInt("selectedIndex", _index)!); - scrollController.addListener(_manageScroll); - } - - void _manageScroll() { - // https://stackoverflow.com/a/75283541 - // Fixes https://github.com/flet-dev/flet/issues/3649 - if (previousIndex != scrollController.selectedItem) { - isScrollDown = previousIndex < scrollController.selectedItem; - isScrollUp = previousIndex > scrollController.selectedItem; - - var previousIndexTemp = previousIndex; - previousIndex = scrollController.selectedItem; - - if (isScrollUp) { - scrollController.jumpToItem(previousIndexTemp - 1); - } else if (isScrollDown) { - scrollController.jumpToItem(previousIndexTemp + 1); - } - } + initialItem: widget.control.getInt("selected_index", 0)!); } @override @@ -72,48 +37,32 @@ class _CupertinoPickerControlState extends State { Widget build(BuildContext context) { debugPrint("CupertinoPicker build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - List ctrls = widget.children.where((c) => c.isVisible).map((c) { - return Center( - child: createControl(widget.control, c.id, disabled, - parentAdaptive: widget.parentAdaptive)); - }).toList(); - - var selectionOverlayCtrl = widget.children - .where((c) => c.isVisible && c.name == "selection_overlay"); - Widget picker = CupertinoPicker( scrollController: scrollController, - backgroundColor: widget.control.attrColor("bgColor", context), - diameterRatio: - widget.control.attrDouble("diameterRatio", _kDefaultDiameterRatio)!, - magnification: widget.control.attrDouble("magnification", 1.0)!, - squeeze: widget.control.attrDouble("squeeze", _kSqueeze)!, - offAxisFraction: widget.control.attrDouble("offAxisFraction", 0.0)!, - itemExtent: widget.control.attrDouble("itemExtent", _kItemExtent)!, - useMagnifier: widget.control.attrBool("useMagnifier", false)!, - looping: widget.control.attrBool("looping", false)!, - selectionOverlay: selectionOverlayCtrl.isNotEmpty - ? createControl( - widget.control, selectionOverlayCtrl.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : CupertinoPickerDefaultSelectionOverlay( - background: widget.control.attrColor( - "defaultSelectionOverlayBgcolor", - context, - CupertinoColors.tertiarySystemFill)!, - ), + backgroundColor: widget.control.getColor("bgcolor", context), + diameterRatio: widget.control.getDouble("diameter_ratio", 1.07)!, + magnification: widget.control.getDouble("magnification", 1.0)!, + squeeze: widget.control.getDouble("squeeze", 1.45)!, + offAxisFraction: widget.control.getDouble("off_axis_fraction", 0.0)!, + itemExtent: widget.control.getDouble("item_extent", 32.0)!, + useMagnifier: widget.control.getBool("use_magnifier", false)!, + looping: widget.control.getBool("looping", false)!, + selectionOverlay: widget.control.buildWidget("selection_overlay") ?? + CupertinoPickerDefaultSelectionOverlay( + background: widget.control.getColor( + "default_selection_overlay_bgcolor", + context, + CupertinoColors.tertiarySystemFill)!, + ), onSelectedItemChanged: (int index) { - _index = index; - widget.backend.updateControlState( - widget.control.id, {"selectedIndex": index.toString()}); - widget.backend - .triggerControlEvent(widget.control.id, "change", index.toString()); + widget.control.updateProperties({"selected_index": index}); + widget.control.triggerEvent("change", index); }, - children: ctrls, + children: widget.control.children("controls").map((c) { + return Center(child: ControlWidget(control: c)); + }).toList(), ); - return constrainedControl(context, picker, widget.parent, widget.control); + return ConstrainedControl(control: widget.control, child: picker); } } diff --git a/packages/flet/lib/src/controls/cupertino_radio.dart b/packages/flet/lib/src/controls/cupertino_radio.dart index b8a641b0f..cfb8a3d2e 100644 --- a/packages/flet/lib/src/controls/cupertino_radio.dart +++ b/packages/flet/lib/src/controls/cupertino_radio.dart @@ -1,28 +1,22 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; import '../utils/colors.dart'; +import '../utils/misc.dart'; import '../utils/mouse.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'error.dart'; -import 'flet_store_mixin.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; +import '../widgets/flet_store_mixin.dart'; +import '../widgets/radio_group_provider.dart'; +import 'base_controls.dart'; import 'list_tile.dart'; class CupertinoRadioControl extends StatefulWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final FletControlBackend backend; - const CupertinoRadioControl( - {super.key, - this.parent, - required this.control, - required this.parentDisabled, - required this.backend}); + CupertinoRadioControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _CupertinoRadioControlState(); @@ -40,8 +34,7 @@ class _CupertinoRadioControlState extends State } void _onFocusChange() { - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); } @override @@ -51,80 +44,72 @@ class _CupertinoRadioControlState extends State super.dispose(); } - void _onChange(String ancestorId, String? value) { - var svalue = value ?? ""; - debugPrint(svalue); - widget.backend.updateControlState(ancestorId, {"value": svalue}); - widget.backend.triggerControlEvent(ancestorId, "change", svalue); + void _onChange(Control radioGroup, String? value) { + radioGroup.updateProperties({"value": value}, notify: true); + radioGroup.triggerEvent("change", value); } @override Widget build(BuildContext context) { debugPrint("CupertinoRadio build: ${widget.control.id}"); - String label = widget.control.attrString("label", "")!; - String value = widget.control.attrString("value", "")!; + String label = widget.control.getString("label", "")!; + String value = widget.control.getString("value", "")!; LabelPosition labelPosition = parseLabelPosition( - widget.control.attrString("labelPosition"), LabelPosition.right)!; - bool autofocus = widget.control.attrBool("autofocus", false)!; - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - return withControlAncestor(widget.control.id, "radiogroup", - (context, viewModel) { - debugPrint("CupertinoRadio build: ${widget.control.id}"); - - if (viewModel.ancestor == null) { - return const ErrorControl( - "CupertinoRadio control must be enclosed with RadioGroup."); - } - - String groupValue = viewModel.ancestor!.attrString("value", "")!; - String ancestorId = viewModel.ancestor!.id; - - var cupertinoRadio = CupertinoRadio( - autofocus: autofocus, - focusNode: _focusNode, - groupValue: groupValue, - value: value, - useCheckmarkStyle: - widget.control.attrBool("useCheckmarkStyle", false)!, - fillColor: widget.control.attrColor("fillColor", context), - focusColor: widget.control.attrColor("focusColor", context), - toggleable: widget.control.attrBool("toggleable", false)!, - mouseCursor: - parseMouseCursor(widget.control.attrString("mouseCursor")), - activeColor: parseColor(Theme.of(context), - widget.control.attrString("activeColor", "primary")!), - inactiveColor: widget.control.attrColor("inactiveColor", context), - onChanged: !disabled - ? (String? value) { - _onChange(ancestorId, value); - } - : null); - - ListTileClicks.of(context)?.notifier.addListener(() { - _onChange(ancestorId, value); - }); - - Widget result = cupertinoRadio; - if (label != "") { - var labelWidget = disabled - ? Text(label, - style: TextStyle(color: Theme.of(context).disabledColor)) - : MouseRegion(cursor: SystemMouseCursors.click, child: Text(label)); - result = MergeSemantics( - child: GestureDetector( - onTap: !disabled - ? () { - _onChange(ancestorId, value); - } - : null, - child: labelPosition == LabelPosition.right - ? Row(children: [cupertinoRadio, labelWidget]) - : Row(children: [labelWidget, cupertinoRadio]))); - } - - return constrainedControl(context, result, widget.parent, widget.control); + widget.control.getString("label_position"), LabelPosition.right)!; + bool autofocus = widget.control.getBool("autofocus", false)!; + + debugPrint("CupertinoRadio build: ${widget.control.id}"); + + var radioGroup = RadioGroupProvider.of(context); + + if (radioGroup == null) { + return const ErrorControl( + "CupertinoRadio must be enclosed within RadioGroup"); + } + + String groupValue = radioGroup.getString("value", "")!; + + var cupertinoRadio = CupertinoRadio( + autofocus: autofocus, + focusNode: _focusNode, + groupValue: groupValue, + value: value, + useCheckmarkStyle: + widget.control.getBool("use_checkmark_style", false)!, + fillColor: widget.control.getColor("fill_color", context), + focusColor: widget.control.getColor("focus_color", context), + toggleable: widget.control.getBool("toggleable", false)!, + mouseCursor: parseMouseCursor(widget.control.getString("mouse_cursor")), + activeColor: widget.control.getColor( + "active_color", context, Theme.of(context).colorScheme.primary)!, + inactiveColor: widget.control.getColor("inactive_color", context), + onChanged: !widget.control.disabled + ? (String? value) => _onChange(radioGroup, value) + : null); + + ListTileClicks.of(context)?.notifier.addListener(() { + _onChange(radioGroup, value); }); + + Widget result = cupertinoRadio; + if (label != "") { + var labelWidget = widget.control.disabled + ? Text(label, + style: TextStyle(color: Theme.of(context).disabledColor)) + : MouseRegion(cursor: SystemMouseCursors.click, child: Text(label)); + result = MergeSemantics( + child: GestureDetector( + onTap: !widget.control.disabled + ? () { + _onChange(radioGroup, value); + } + : null, + child: labelPosition == LabelPosition.right + ? Row(children: [cupertinoRadio, labelWidget]) + : Row(children: [labelWidget, cupertinoRadio]))); + } + + return ConstrainedControl(control: widget.control, child: result); } } diff --git a/packages/flet/lib/src/controls/cupertino_segmented_button.dart b/packages/flet/lib/src/controls/cupertino_segmented_button.dart index 7c50175b4..8dcbcb473 100644 --- a/packages/flet/lib/src/controls/cupertino_segmented_button.dart +++ b/packages/flet/lib/src/controls/cupertino_segmented_button.dart @@ -1,28 +1,18 @@ import 'package:flutter/cupertino.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; -import 'create_control.dart'; -import 'error.dart'; -import 'flet_store_mixin.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; class CupertinoSegmentedButtonControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool? parentAdaptive; - final bool parentDisabled; - final FletControlBackend backend; - const CupertinoSegmentedButtonControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentAdaptive, - required this.parentDisabled, - required this.backend}); + CupertinoSegmentedButtonControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => @@ -30,51 +20,41 @@ class CupertinoSegmentedButtonControl extends StatefulWidget { } class _CupertinoSegmentedButtonControlState - extends State with FletStoreMixin { + extends State { @override Widget build(BuildContext context) { debugPrint("CupertinoSegmentedButtonControl build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - List ctrls = widget.children.where((c) => c.isVisible).toList(); - int? selectedIndex = widget.control.attrInt("selectedIndex"); + var controls = widget.control.buildWidgets("controls"); + var selectedIndex = widget.control.getInt("selected_index"); - if (ctrls.length < 2) { + if (controls.length < 2) { return const ErrorControl( "CupertinoSegmentedButton must have at minimum two visible controls"); } - var children = ctrls.asMap().map((i, c) => MapEntry( - i, - createControl(widget.control, c.id, disabled, - parentAdaptive: adaptive))); - return constrainedControl( - context, - CupertinoSegmentedControl( - children: children, - groupValue: selectedIndex, - onValueChanged: (int index) { - if (!disabled) { - widget.backend.updateControlState( - widget.control.id, {"selectedIndex": index.toString()}); - widget.backend.triggerControlEvent( - widget.control.id, "change", index.toString()); - setState(() { - selectedIndex = index; - }); - } - }, - borderColor: widget.control.attrColor("borderColor", context), - selectedColor: widget.control.attrColor("selectedColor", context), - unselectedColor: widget.control.attrColor("unselectedColor", context), - pressedColor: widget.control.attrColor("clickColor", context), - disabledColor: widget.control.attrColor("disabledColor", context), - disabledTextColor: widget.control.attrColor("disabledTextColor", context), - padding: parseEdgeInsets(widget.control, "padding"), - ), - widget.parent, - widget.control); + var segmnetedButton = CupertinoSegmentedControl( + groupValue: selectedIndex, + borderColor: widget.control.getColor("border_color", context), + selectedColor: widget.control.getColor("selected_color", context), + unselectedColor: widget.control.getColor("unselected_color", context), + pressedColor: widget.control.getColor("click_color", context), + disabledColor: widget.control.getColor("disabled_color", context), + disabledTextColor: + widget.control.getColor("disabled_text_color", context), + padding: widget.control.getPadding("padding"), + children: controls.asMap().map((i, c) => MapEntry(i, c)), + onValueChanged: (int index) { + if (!widget.control.disabled) { + widget.control.updateProperties({"selected_index": index}); + widget.control.triggerEvent("change", index); + setState(() { + selectedIndex = index; + }); + } + }, + ); + + return ConstrainedControl(control: widget.control, child: segmnetedButton); } } diff --git a/packages/flet/lib/src/controls/cupertino_slider.dart b/packages/flet/lib/src/controls/cupertino_slider.dart index 2b6ca75b4..0e93fd3ff 100644 --- a/packages/flet/lib/src/controls/cupertino_slider.dart +++ b/packages/flet/lib/src/controls/cupertino_slider.dart @@ -1,23 +1,18 @@ import 'package:flutter/cupertino.dart'; -import '../flet_control_backend.dart'; +import '../flet_backend.dart'; import '../models/control.dart'; +import '../utils/colors.dart'; import '../utils/debouncer.dart'; +import '../utils/numbers.dart'; import '../utils/platform.dart'; -import 'create_control.dart'; +import 'base_controls.dart'; class CupertinoSliderControl extends StatefulWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final FletControlBackend backend; - const CupertinoSliderControl( - {super.key, - this.parent, - required this.control, - required this.parentDisabled, - required this.backend}); + CupertinoSliderControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _CupertinoSliderControlState(); @@ -26,6 +21,7 @@ class CupertinoSliderControl extends StatefulWidget { class _CupertinoSliderControlState extends State { double _value = 0; final _debouncer = Debouncer(milliseconds: isDesktopPlatform() ? 10 : 100); + late FletBackend backend; @override void dispose() { @@ -34,14 +30,12 @@ class _CupertinoSliderControlState extends State { } void onChange(double value) { - var svalue = value.toString(); - debugPrint(svalue); _value = value; - var props = {"value": svalue}; - widget.backend.updateControlState(widget.control.id, props, server: false); + var props = {"value": value}; + widget.control.updateProperties(props, python: false, notify: true); _debouncer.run(() { - widget.backend.updateControlState(widget.control.id, props); - widget.backend.triggerControlEvent(widget.control.id, "change"); + widget.control.updateProperties(props, notify: true); + widget.control.triggerEvent("change"); }); } @@ -49,13 +43,10 @@ class _CupertinoSliderControlState extends State { Widget build(BuildContext context) { debugPrint("CupertinoSliderControl build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; + double min = widget.control.getDouble("min", 0.0)!; + double max = widget.control.getDouble("max", 1.0)!; - double min = widget.control.attrDouble("min", 0)!; - double max = widget.control.attrDouble("max", 1)!; - int? divisions = widget.control.attrInt("divisions"); - - double value = widget.control.attrDouble("value", min)!; + double value = widget.control.getDouble("value", min)!; if (_value != value) { // verify limits if (value < min) { @@ -71,29 +62,23 @@ class _CupertinoSliderControlState extends State { value: _value, min: min, max: max, - divisions: divisions, - activeColor: widget.control.attrColor("activeColor", context), - thumbColor: widget.control.attrColor("thumbColor", context) ?? - CupertinoColors.white, - onChanged: !disabled - ? (double value) { - onChange(value); - } - : null, - onChangeStart: !disabled + divisions: widget.control.getInt("divisions"), + activeColor: widget.control.getColor("active_color", context), + thumbColor: widget.control + .getColor("thumb_color", context, CupertinoColors.white)!, + onChanged: + !widget.control.disabled ? (double value) => onChange(value) : null, + onChangeStart: !widget.control.disabled ? (double value) { - widget.backend.triggerControlEvent( - widget.control.id, "change_start", value.toString()); + widget.control.triggerEvent("change_start", value); } : null, - onChangeEnd: !disabled + onChangeEnd: !widget.control.disabled ? (double value) { - widget.backend.triggerControlEvent( - widget.control.id, "change_end", value.toString()); + widget.control.triggerEvent("change_end", value); } : null); - return constrainedControl( - context, cupertinoSlider, widget.parent, widget.control); + return ConstrainedControl(control: widget.control, child: cupertinoSlider); } } diff --git a/packages/flet/lib/src/controls/cupertino_sliding_segmented_button.dart b/packages/flet/lib/src/controls/cupertino_sliding_segmented_button.dart index 847e04baa..35cc657b6 100644 --- a/packages/flet/lib/src/controls/cupertino_sliding_segmented_button.dart +++ b/packages/flet/lib/src/controls/cupertino_sliding_segmented_button.dart @@ -1,80 +1,54 @@ import 'package:flutter/cupertino.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; -import 'create_control.dart'; -import 'error.dart'; -import 'flet_store_mixin.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; -class CupertinoSlidingSegmentedButtonControl extends StatefulWidget { - final Control? parent; +class CupertinoSlidingSegmentedButtonControl extends StatelessWidget { final Control control; - final List children; - final bool? parentAdaptive; - final bool parentDisabled; - final FletControlBackend backend; const CupertinoSlidingSegmentedButtonControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentAdaptive, - required this.parentDisabled, - required this.backend}); + {super.key, required this.control}); @override - State createState() => - _CupertinoSlidingSegmentedButtonControlState(); -} - -class _CupertinoSlidingSegmentedButtonControlState - extends State with FletStoreMixin { - @override Widget build(BuildContext context) { - debugPrint( - "CupertinoSlidingSegmentedButtonControl build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; + debugPrint("CupertinoSlidingSegmentedButtonControl build: ${control.id}"); - List ctrls = widget.children.where((c) => c.isVisible).toList(); + var controls = control.buildWidgets("controls"); - if (ctrls.length < 2) { + if (controls.length < 2) { return const ErrorControl( "CupertinoSlidingSegmentedButton must have at minimum two visible controls"); } - Map children = ctrls.asMap().map((i, c) => MapEntry( - i, - createControl(widget.control, c.id, disabled, - parentAdaptive: adaptive))); var button = CupertinoSlidingSegmentedControl( - children: children, - groupValue: widget.control.attrInt("selectedIndex"), - onValueChanged: (int? index) { - if (!disabled) { - widget.backend.updateControlState(widget.control.id, - {"selectedIndex": index != null ? index.toString() : ""}); - widget.backend.triggerControlEvent( - widget.control.id, "change", index?.toString()); - } - }, - proportionalWidth: widget.control.attrBool("proportionalWidth", false)!, - thumbColor: widget.control.attrColor( - "thumbColor", + groupValue: control.getInt("selected_index"), + proportionalWidth: control.getBool("proportional_width", false)!, + backgroundColor: control.getColor( + "bgcolor", context, CupertinoColors.tertiarySystemFill)!, + padding: control.getPadding( + "padding", const EdgeInsets.symmetric(vertical: 2, horizontal: 3))!, + thumbColor: control.getColor( + "thumb_color", context, const CupertinoDynamicColor.withBrightness( color: Color(0xFFFFFFFF), darkColor: Color(0xFF636366), ))!, - backgroundColor: widget.control - .attrColor("bgColor", context, CupertinoColors.tertiarySystemFill)!, - padding: parseEdgeInsets(widget.control, "padding", - const EdgeInsets.symmetric(vertical: 2, horizontal: 3))!, + children: controls.asMap().map((i, c) => MapEntry(i, c)), + onValueChanged: (int? index) { + if (!control.disabled) { + control + .updateProperties({"selected_index": index ?? 0}, notify: true); + control.triggerEvent("change", index); + } + }, ); - return constrainedControl(context, button, widget.parent, widget.control); + return ConstrainedControl(control: control, child: button); } } diff --git a/packages/flet/lib/src/controls/cupertino_switch.dart b/packages/flet/lib/src/controls/cupertino_switch.dart index e2a5f52b2..0c3382a63 100644 --- a/packages/flet/lib/src/controls/cupertino_switch.dart +++ b/packages/flet/lib/src/controls/cupertino_switch.dart @@ -1,36 +1,26 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; import '../utils/box.dart'; import '../utils/colors.dart'; import '../utils/icons.dart'; +import '../utils/misc.dart'; import '../utils/numbers.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'flet_store_mixin.dart'; +import 'base_controls.dart'; import 'list_tile.dart'; class CupertinoSwitchControl extends StatefulWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final FletControlBackend backend; - const CupertinoSwitchControl( - {super.key, - this.parent, - required this.control, - required this.parentDisabled, - required this.backend}); + CupertinoSwitchControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _CupertinoSwitchControlState(); } -class _CupertinoSwitchControlState extends State - with FletStoreMixin { +class _CupertinoSwitchControlState extends State { bool _value = false; late final FocusNode _focusNode; @@ -49,110 +39,104 @@ class _CupertinoSwitchControlState extends State } void _onChange(bool value) { - var svalue = value.toString(); - debugPrint(svalue); _value = value; - widget.backend.updateControlState(widget.control.id, {"value": svalue}); - widget.backend.triggerControlEvent(widget.control.id, "change", svalue); + var props = {"value": value}; + widget.control.updateProperties(props, notify: true); + widget.control.triggerEvent("change"); } void _onFocusChange() { - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); } @override Widget build(BuildContext context) { debugPrint("CupertinoSwitchControl build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - String label = widget.control.attrString("label", "")!; - LabelPosition labelPosition = parseLabelPosition( - widget.control.attrString("labelPosition"), LabelPosition.right)!; - bool autofocus = widget.control.attrBool("autofocus", false)!; + String label = widget.control.getString("label", "")!; + LabelPosition labelPosition = + widget.control.getLabelPosition("label_position", LabelPosition.right)!; + bool autofocus = widget.control.getBool("autofocus", false)!; - bool value = widget.control.attrBool("value", false)!; + bool value = widget.control.getBool("value", false)!; if (_value != value) { _value = value; } + ThemeData theme = Theme.of(context); + var materialThumbColor = - parseWidgetStateColor(Theme.of(context), widget.control, "thumbColor"); - - var materialTrackColor = - parseWidgetStateColor(Theme.of(context), widget.control, "trackColor"); - var activeThumbImage = widget.control.attrString("activeThumbImage"); - var inactiveThumbImage = widget.control.attrString("inactiveThumbImage"); - - return withPageArgs((context, pageArgs) { - var swtch = CupertinoSwitch( - autofocus: autofocus, - focusNode: _focusNode, - activeTrackColor: - widget.control.attrColor("activeTrackColor", context) ?? - widget.control.attrColor("activeColor", context), - // TODO: deprecated in v0.26.0, remove in v0.29.0 - thumbColor: materialThumbColor?.resolve({}), - trackColor: materialTrackColor?.resolve({}), - focusColor: widget.control.attrColor("focusColor", context), - inactiveTrackColor: - widget.control.attrColor("inactiveTrackColor", context), - inactiveThumbColor: - widget.control.attrColor("inactiveThumbColor", context), - trackOutlineColor: parseWidgetStateColor( - Theme.of(context), widget.control, "trackOutlineColor"), - trackOutlineWidth: - parseWidgetStateDouble(widget.control, "trackOutlineWidth"), - thumbIcon: parseWidgetStateIcon( - Theme.of(context), widget.control, "thumbIcon"), - inactiveThumbImage: getImageProvider( - inactiveThumbImage, inactiveThumbImage, pageArgs), - activeThumbImage: - getImageProvider(activeThumbImage, activeThumbImage, pageArgs), - onActiveThumbImageError: activeThumbImage == null - ? null - : (Object exception, StackTrace? stackTrace) { - widget.backend.triggerControlEvent( - widget.control.id, "image_error", exception.toString()); - }, - onInactiveThumbImageError: inactiveThumbImage == null - ? null - : (Object exception, StackTrace? stackTrace) { - widget.backend.triggerControlEvent( - widget.control.id, "image_error", exception.toString()); - }, - value: _value, - offLabelColor: widget.control.attrColor("offLabelColor", context), - onLabelColor: widget.control.attrColor("onLabelColor", context), - onChanged: !disabled - ? (bool value) { - _onChange(value); - } - : null); - - ListTileClicks.of(context)?.notifier.addListener(() { - _onChange(!_value); - }); - - Widget result = swtch; - if (label != "") { - var labelWidget = disabled - ? Text(label, - style: TextStyle(color: Theme.of(context).disabledColor)) - : MouseRegion(cursor: SystemMouseCursors.click, child: Text(label)); - result = MergeSemantics( - child: GestureDetector( - onTap: !disabled - ? () { - _onChange(!_value); - } - : null, - child: labelPosition == LabelPosition.right - ? Row(children: [swtch, labelWidget]) - : Row(children: [labelWidget, swtch]))); - } - - return constrainedControl(context, result, widget.parent, widget.control); + widget.control.getWidgetStateColor("thumb_color", theme); + // var materialTrackColor = + // widget.control.getWidgetStateColor("track_color", theme); + var activeThumbImage = widget.control.getString("active_thumb_image"); + var inactiveThumbImage = widget.control.getString("inactive_thumb_image"); + + var swtch = CupertinoSwitch( + autofocus: autofocus, + focusNode: _focusNode, + activeTrackColor: + widget.control.getColor("active_track_color", context), + thumbColor: materialThumbColor?.resolve({}), + //inactiveTrackColor: materialTrackColor?.resolve({}), + focusColor: widget.control.getColor("focusColor", context), + inactiveTrackColor: + widget.control.getColor("inactive_track_color", context), + inactiveThumbColor: + widget.control.getColor("inactive_thumb_color", context), + trackOutlineColor: + widget.control.getWidgetStateColor("track_outline_color", theme), + trackOutlineWidth: + widget.control.getWidgetStateDouble("track_outline_width"), + thumbIcon: widget.control.getWidgetStateIcon("thumb_icon", theme), + inactiveThumbImage: + getImageProvider(context, inactiveThumbImage, null, null), + activeThumbImage: + getImageProvider(context, activeThumbImage, null, null), + onActiveThumbImageError: activeThumbImage == null + ? null + : (Object exception, StackTrace? stackTrace) { + widget.control + .triggerEvent("image_error", exception.toString()); + }, + onInactiveThumbImageError: inactiveThumbImage == null + ? null + : (Object exception, StackTrace? stackTrace) { + widget.control + .triggerEvent("image_error", exception.toString()); + }, + value: _value, + offLabelColor: widget.control.getColor("off_label_color", context), + onLabelColor: widget.control.getColor("on_label_color", context), + onChanged: !widget.control.disabled + ? (bool value) { + _onChange(value); + } + : null); + + ListTileClicks.of(context)?.notifier.addListener(() { + _onChange(!_value); }); + + Widget result = swtch; + if (label != "") { + var labelWidget = widget.control.disabled + ? Text(label, + style: TextStyle(color: Theme.of(context).disabledColor)) + : MouseRegion(cursor: SystemMouseCursors.click, child: Text(label)); + result = MergeSemantics( + child: GestureDetector( + onTap: !widget.control.disabled + ? () { + _onChange(!_value); + } + : null, + child: labelPosition == LabelPosition.right + ? Row(children: [swtch, labelWidget]) + : Row(children: [labelWidget, swtch]))); + } + + return ConstrainedControl(control: widget.control, child: result); + //}); } } diff --git a/packages/flet/lib/src/controls/cupertino_textfield.dart b/packages/flet/lib/src/controls/cupertino_textfield.dart index 551a9b48d..b120e7f7e 100644 --- a/packages/flet/lib/src/controls/cupertino_textfield.dart +++ b/packages/flet/lib/src/controls/cupertino_textfield.dart @@ -1,48 +1,20 @@ +import 'package:flet/flet.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import '../flet_control_backend.dart'; -import '../models/control.dart'; -import '../utils/autofill.dart'; -import '../utils/borders.dart'; -import '../utils/box.dart'; -import '../utils/edge_insets.dart'; -import '../utils/form_field.dart'; -import '../utils/gradient.dart'; -import '../utils/images.dart'; -import '../utils/others.dart'; -import '../utils/overlay_style.dart'; -import '../utils/text.dart'; -import '../utils/textfield.dart'; -import 'create_control.dart'; -import 'flet_store_mixin.dart'; -import 'textfield.dart'; - class CupertinoTextFieldControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const CupertinoTextFieldControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + CupertinoTextFieldControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _CupertinoTextFieldControlState(); } -class _CupertinoTextFieldControlState extends State - with FletStoreMixin { +class _CupertinoTextFieldControlState extends State { String _value = ""; bool _focused = false; bool _revealPassword = false; @@ -61,7 +33,7 @@ class _CupertinoTextFieldControlState extends State if (!HardwareKeyboard.instance.isShiftPressed && evt.logicalKey.keyLabel == 'Enter') { if (evt is KeyDownEvent) { - widget.backend.triggerControlEvent(widget.control.id, "submit"); + widget.control.triggerEvent("submit"); } return KeyEventResult.handled; } else { @@ -88,93 +60,72 @@ class _CupertinoTextFieldControlState extends State setState(() { _focused = _shiftEnterfocusNode.hasFocus; }); - widget.backend.triggerControlEvent( - widget.control.id, _shiftEnterfocusNode.hasFocus ? "focus" : "blur"); + widget.control + .triggerEvent(_shiftEnterfocusNode.hasFocus ? "focus" : "blur"); } void _onFocusChange() { setState(() { _focused = _focusNode.hasFocus; }); - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); } @override Widget build(BuildContext context) { debugPrint("CupertinoTextField build: ${widget.control.id}"); - bool autofocus = widget.control.attrBool("autofocus", false)!; - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - debugPrint("CupertinoTextField StoreConnector build: ${widget.control.id}"); - - String value = widget.control.attrs["value"] ?? ""; + var autofocus = widget.control.getBool("autofocus", false)!; + var value = widget.control.getString("value", "")!; if (_value != value) { _value = value; _controller.text = value; } - var prefixControls = - widget.children.where((c) => c.name == "prefix" && c.isVisible); - var suffixControls = - widget.children.where((c) => c.name == "suffix" && c.isVisible); + var shiftEnter = widget.control.getBool("shift_enter", false)!; + var multiline = widget.control.getBool("multiline", false)! || shiftEnter; + var minLines = widget.control.getInt("min_lines", 1)!; + var maxLines = widget.control.getInt("max_lines", multiline ? null : 1); - bool shiftEnter = widget.control.attrBool("shiftEnter", false)!; - bool multiline = widget.control.attrBool("multiline", false)! || shiftEnter; - int minLines = widget.control.attrInt("minLines", 1)!; - int? maxLines = widget.control.attrInt("maxLines", multiline ? null : 1); + var readOnly = widget.control.getBool("read_only", false)!; + var password = widget.control.getBool("password", false)!; + var onChange = widget.control.getBool("on_change", false)!; - bool readOnly = widget.control.attrBool("readOnly", false)!; - bool password = widget.control.attrBool("password", false)!; - bool onChange = widget.control.attrBool("onChange", false)!; + var cursorColor = widget.control.getColor("cursor_color", context); + var selectionColor = widget.control.getColor("selection_color", context); - var cursorColor = widget.control.attrColor("cursorColor", context); - var selectionColor = widget.control.attrColor("selectionColor", context); + var maxLength = widget.control.getInt("max_length"); - int? maxLength = widget.control.attrInt("maxLength"); + var textSize = widget.control.getDouble("text_size"); - var textSize = widget.control.attrDouble("textSize"); + var color = widget.control.getColor("color", context); + var focusedColor = widget.control.getColor("focused_color", context); - var color = widget.control.attrColor("color", context); - var focusedColor = widget.control.attrColor("focusedColor", context); - - TextStyle? textStyle = - parseTextStyle(Theme.of(context), widget.control, "textStyle"); + var textStyle = + widget.control.getTextStyle("text_style", Theme.of(context)); if (textSize != null || color != null || focusedColor != null) { textStyle = (textStyle ?? const TextStyle()).copyWith( fontSize: textSize, color: _focused ? focusedColor ?? color : color); } - TextCapitalization textCapitalization = parseTextCapitalization( - widget.control.attrString("textCapitalization"), - TextCapitalization.none)!; - - FilteringTextInputFormatter? inputFilter = - parseInputFilter(widget.control, "inputFilter"); - - List? inputFormatters = []; - // add non-null input formatters + List inputFormatters = []; + var inputFilter = widget.control.getTextInputFormatter("input_filter"); if (inputFilter != null) { inputFormatters.add(inputFilter); } + var textCapitalization = widget.control + .getTextCapitalization("capitalization", TextCapitalization.none)!; if (textCapitalization != TextCapitalization.none) { inputFormatters.add(TextCapitalizationFormatter(textCapitalization)); } - - TextAlign textAlign = parseTextAlign( - widget.control.attrString("textAlign"), TextAlign.start)!; - - double? textVerticalAlign = widget.control.attrDouble("textVerticalAlign"); - - bool rtl = widget.control.attrBool("rtl", false)!; - bool autocorrect = widget.control.attrBool("autocorrect", true)!; - ; - + var textAlign = widget.control.getTextAlign("text_align", TextAlign.start)!; + var textVerticalAlign = widget.control.getDouble("text_vertical_align"); + var rtl = widget.control.getBool("rtl", false)!; + var autocorrect = widget.control.getBool("autocorrect", true)!; FocusNode focusNode = shiftEnter ? _shiftEnterfocusNode : _focusNode; - var focusValue = widget.control.attrString("focus"); - var blurValue = widget.control.attrString("blur"); + var focusValue = widget.control.getString("focus"); + var blurValue = widget.control.getString("blur"); if (focusValue != null && focusValue != _lastFocusValue) { _lastFocusValue = focusValue; focusNode.requestFocus(); @@ -184,20 +135,19 @@ class _CupertinoTextFieldControlState extends State _focusNode.unfocus(); } - BorderRadius? borderRadius = - parseBorderRadius(widget.control, "borderRadius"); + var borderRadius = widget.control.getBorderRadius("border_radius"); BoxBorder? border; - double borderWidth = widget.control.attrDouble("borderWidth") ?? 1.0; - Color borderColor = widget.control.attrColor("borderColor", context) ?? + var borderWidth = widget.control.getDouble("border_width", 1.0)!; + var borderColor = widget.control.getColor("border_color", context) ?? const Color(0xFF000000); try { - border = parseBorder(Theme.of(context), widget.control, "border"); + border = widget.control.getBorder("border", Theme.of(context)); // adaptive TextField is being created } catch (e) { FormFieldInputBorder inputBorder = parseFormFieldInputBorder( - widget.control.attrString("border"), + widget.control.getString("border"), FormFieldInputBorder.outline, )!; @@ -210,17 +160,17 @@ class _CupertinoTextFieldControlState extends State } } - bool canRevealPassword = - widget.control.attrBool("canRevealPassword", false)!; + var canRevealPassword = + widget.control.getBool("can_reveal_password", false)!; Widget? revealPasswordIcon; if (password && canRevealPassword) { revealPasswordIcon = Padding( padding: const EdgeInsets.only(right: 15.0), child: GestureDetector( - child: Icon( - _revealPassword ? CupertinoIcons.eye_slash : CupertinoIcons.eye, - ), + child: Icon(_revealPassword + ? CupertinoIcons.eye_slash + : CupertinoIcons.eye), onTap: () { setState(() { _revealPassword = !_revealPassword; @@ -228,145 +178,138 @@ class _CupertinoTextFieldControlState extends State }), ); } - var fitParentSize = widget.control.attrBool("fitParentSize", false)!; - BoxDecoration? defaultDecoration = const CupertinoTextField().decoration; - var gradient = parseGradient(Theme.of(context), widget.control, "gradient"); - var blendMode = parseBlendMode(widget.control.attrString("blendMode")); - - var bgColor = widget.control.attrColor("bgColor", context); + var fitParentSize = widget.control.getBool("fit_parent_size", false)!; + var defaultDecoration = const CupertinoTextField().decoration; + var gradient = widget.control.getGradient("gradient", Theme.of(context)); + var blendMode = widget.control.getBlendMode("blend_mode"); + var bgcolor = widget.control.getColor("bgcolor", context); + var label = widget.control.get("label"); + String? labelStr; + if (label is String) { + labelStr = widget.control.getString("label"); + } - return withPageArgs((context, pageArgs) { - Widget textField = CupertinoTextField( - style: textStyle, - textAlignVertical: textVerticalAlign != null - ? TextAlignVertical(y: textVerticalAlign) - : null, - placeholder: widget.control.attrString("placeholderText") ?? - widget.control.attrString("label"), - // use label for adaptive TextField - placeholderStyle: parseTextStyle(Theme.of(context), widget.control, "placeholderStyle") ?? - parseTextStyle(Theme.of(context), widget.control, "labelStyle"), - // labelStyle for adaptive TextField - autofocus: autofocus, - enabled: !disabled, - onSubmitted: !multiline - ? (String value) { - widget.backend - .triggerControlEvent(widget.control.id, "submit", value); - } - : null, - decoration: defaultDecoration?.copyWith( - color: bgColor, - gradient: gradient, - image: parseDecorationImage( - Theme.of(context), widget.control, "image", pageArgs), - backgroundBlendMode: - bgColor != null || gradient != null ? blendMode : null, - border: border, - borderRadius: borderRadius, - boxShadow: - parseBoxShadow(Theme.of(context), widget.control, "shadow")), - cursorHeight: widget.control.attrDouble("cursorHeight"), - showCursor: widget.control.attrBool("showCursor"), - cursorWidth: widget.control.attrDouble("cursorWidth", 2.0)!, - cursorRadius: parseRadius( - widget.control, "cursorRadius", const Radius.circular(2.0))!, - keyboardType: multiline - ? TextInputType.multiline - : parseTextInputType(widget.control.attrString("keyboardType"), - TextInputType.text)!, - clearButtonSemanticLabel: - widget.control.attrString("clearButtonSemanticsLabel"), - autocorrect: autocorrect, - enableSuggestions: - widget.control.attrBool("enableSuggestions", true)!, - smartDashesType: widget.control.attrBool("smartDashesType", true)! - ? SmartDashesType.enabled - : SmartDashesType.disabled, - smartQuotesType: widget.control.attrBool("smartQuotesType", true)! - ? SmartQuotesType.enabled - : SmartQuotesType.disabled, - suffixMode: parseVisibilityMode( - widget.control.attrString("suffixVisibilityMode"), - OverlayVisibilityMode.always)!, - prefixMode: parseVisibilityMode( - widget.control.attrString("prefixVisibilityMode"), - OverlayVisibilityMode.always)!, - textAlign: textAlign, - minLines: fitParentSize ? null : minLines, - maxLines: fitParentSize ? null : maxLines, - maxLength: maxLength, - prefix: prefixControls.isNotEmpty - ? createControl(widget.control, prefixControls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : null, - suffix: revealPasswordIcon ?? (suffixControls.isNotEmpty ? createControl(widget.control, suffixControls.first.id, disabled, parentAdaptive: widget.parentAdaptive) : null), - readOnly: readOnly, - textDirection: rtl ? TextDirection.rtl : null, - inputFormatters: inputFormatters.isNotEmpty ? inputFormatters : null, - obscureText: password && !_revealPassword, - padding: parseEdgeInsets(widget.control, "padding", const EdgeInsets.all(7.0))!, - scribbleEnabled: widget.control.attrBool("enableScribble", true)!, - scrollPadding: parseEdgeInsets(widget.control, "scrollPadding", const EdgeInsets.all(20.0))!, - obscuringCharacter: widget.control.attrString("obscuringCharacter", '•')!, - cursorOpacityAnimates: widget.control.attrBool("animateCursorOpacity", Theme.of(context).platform == TargetPlatform.iOS)!, - expands: fitParentSize, - enableIMEPersonalizedLearning: widget.control.attrBool("enableIMEPersonalizedLearning", true)!, - clipBehavior: parseClip(widget.control.attrString("clipBehavior"), Clip.hardEdge)!, - cursorColor: cursorColor, - autofillHints: parseAutofillHints(widget.control, "autofillHints"), - keyboardAppearance: parseBrightness(widget.control.attrString("keyboardBrightness")), - enableInteractiveSelection: widget.control.attrBool("enableInteractiveSelection"), - clearButtonMode: parseVisibilityMode(widget.control.attrString("clearButtonVisibilityMode"), OverlayVisibilityMode.never)!, - strutStyle: parseStrutStyle(widget.control, "strutStyle"), - onTap: () { - widget.backend.triggerControlEvent(widget.control.id, "click"); - }, - controller: _controller, - focusNode: focusNode, - onTapOutside: widget.control.attrBool("onTapOutside", false)! - ? (PointerDownEvent? event) { - widget.backend - .triggerControlEvent(widget.control.id, "tapOutside"); - } - : null, - onChanged: (String value) { - //debugPrint(value); - _value = value; - widget.backend - .updateControlState(widget.control.id, {"value": value}); - if (onChange) { - widget.backend - .triggerControlEvent(widget.control.id, "change", value); - } - }); + Widget textField = CupertinoTextField( + style: textStyle, + textAlignVertical: textVerticalAlign != null + ? TextAlignVertical(y: textVerticalAlign) + : null, + placeholder: widget.control.getString("placeholder_text") ?? labelStr, + placeholderStyle: + widget.control.getTextStyle("placeholder_style", Theme.of(context)) ?? + widget.control.getTextStyle("label_style", Theme.of(context)), + // label_style for adaptive TextField + autofocus: autofocus, + enabled: !widget.control.disabled, + onSubmitted: !multiline + ? (String value) { + widget.control.triggerEvent("submit", value); + } + : null, + decoration: defaultDecoration?.copyWith( + color: bgcolor, + gradient: gradient, + image: widget.control.getDecorationImage("image", context), + backgroundBlendMode: + bgcolor != null || gradient != null ? blendMode : null, + border: border, + borderRadius: borderRadius, + boxShadow: + widget.control.getBoxShadows("shadow", Theme.of(context))), + cursorHeight: widget.control.getDouble("cursor_height"), + showCursor: widget.control.getBool("show_cursor"), + cursorWidth: widget.control.getDouble("cursor_width", 2.0)!, + cursorRadius: widget.control + .getRadius("cursor_radius", const Radius.circular(2.0))!, + keyboardType: multiline + ? TextInputType.multiline + : widget.control + .getTextInputType("keyboard_type", TextInputType.text)!, + clearButtonSemanticLabel: + widget.control.getString("clear_button_semantics_label"), + autocorrect: autocorrect, + enableSuggestions: widget.control.getBool("enable_suggestions", true)!, + smartDashesType: widget.control.getBool("smart_dashes_type", true)! + ? SmartDashesType.enabled + : SmartDashesType.disabled, + smartQuotesType: widget.control.getBool("smart_quotes_type", true)! + ? SmartQuotesType.enabled + : SmartQuotesType.disabled, + suffixMode: widget.control.getOverlayVisibilityMode( + "suffix_visibility_mode", OverlayVisibilityMode.always)!, + prefixMode: widget.control.getOverlayVisibilityMode( + "prefix_visibility_mode", OverlayVisibilityMode.always)!, + textAlign: textAlign, + minLines: fitParentSize ? null : minLines, + maxLines: fitParentSize ? null : maxLines, + maxLength: maxLength, + prefix: widget.control.buildTextOrWidget("prefix"), + suffix: + revealPasswordIcon ?? widget.control.buildTextOrWidget("suffix"), + readOnly: readOnly, + textDirection: rtl ? TextDirection.rtl : null, + inputFormatters: inputFormatters.isNotEmpty ? inputFormatters : null, + obscureText: password && !_revealPassword, + padding: + widget.control.getPadding("padding", const EdgeInsets.all(7.0))!, + stylusHandwritingEnabled: + widget.control.getBool("enable_stylus_handwriting", true)!, + scrollPadding: widget.control + .getPadding("scroll_padding", const EdgeInsets.all(20.0))!, + obscuringCharacter: + widget.control.getString("obscuring_character", '•')!, + cursorOpacityAnimates: + widget.control.getBool("animate_cursor_opacity", isIOSMobile())!, + expands: fitParentSize, + enableIMEPersonalizedLearning: + widget.control.getBool("enable_ime_personalized_learning", true)!, + clipBehavior: + widget.control.getClipBehavior("clip_behavior", Clip.hardEdge)!, + cursorColor: cursorColor, + autofillHints: parseAutofillHints(widget.control.get("autofill_hints")), + keyboardAppearance: widget.control.getBrightness("keyboard_brightness"), + enableInteractiveSelection: + widget.control.getBool("enable_interactive_selection"), + clearButtonMode: widget.control.getOverlayVisibilityMode("clear_button_visibility_mode", OverlayVisibilityMode.never)!, + strutStyle: widget.control.getStrutStyle("strut_style"), + onTap: () => widget.control.triggerEvent("click"), + controller: _controller, + focusNode: focusNode, + onTapOutside: widget.control.getBool("on_tap_outside", false)! + ? (PointerDownEvent? event) { + widget.control.triggerEvent("tap_outside"); + } + : null, + onChanged: (String value) { + _value = value; + widget.control.updateProperties({"value": value}); + if (onChange) { + widget.control.triggerEvent("change", value); + } + }); - if (cursorColor != null || selectionColor != null) { - textField = TextSelectionTheme( - data: TextSelectionTheme.of(context).copyWith( - cursorColor: cursorColor, selectionColor: selectionColor), - child: textField); - } + if (cursorColor != null || selectionColor != null) { + textField = TextSelectionTheme( + data: TextSelectionTheme.of(context).copyWith( + cursorColor: cursorColor, selectionColor: selectionColor), + child: textField); + } - if (widget.control.attrInt("expand", 0)! > 0) { - return constrainedControl( - context, textField, widget.parent, widget.control); - } else { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - if (constraints.maxWidth == double.infinity && - widget.control.attrDouble("width") == null) { - textField = ConstrainedBox( + if (widget.control.get("expand") == true || + (widget.control.get("expand") is int && + widget.control.getInt("expand", 0)! > 0)) { + return ConstrainedControl(control: widget.control, child: textField); + } else { + double? width = widget.control.getDouble("width"); + + return ConstrainedControl( + control: widget.control, + child: width == null + ? ConstrainedBox( constraints: const BoxConstraints.tightFor(width: 300), - child: textField, - ); - } - - return constrainedControl( - context, textField, widget.parent, widget.control); - }, - ); - } - }); + child: textField) + : textField, + ); + } } } diff --git a/packages/flet/lib/src/controls/cupertino_timer_picker.dart b/packages/flet/lib/src/controls/cupertino_timer_picker.dart index bde0d47db..276f326de 100644 --- a/packages/flet/lib/src/controls/cupertino_timer_picker.dart +++ b/packages/flet/lib/src/controls/cupertino_timer_picker.dart @@ -1,24 +1,17 @@ import 'package:flutter/cupertino.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; import '../utils/alignment.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; +import '../utils/colors.dart'; +import '../utils/numbers.dart'; +import '../utils/time.dart'; +import 'base_controls.dart'; class CupertinoTimerPickerControl extends StatefulWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final FletControlBackend backend; - - const CupertinoTimerPickerControl( - {super.key, - this.parent, - required this.control, - required this.parentDisabled, - required this.backend}); + CupertinoTimerPickerControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => @@ -31,26 +24,25 @@ class _CupertinoTimerPickerControlState Widget build(BuildContext context) { debugPrint("CupertinoTimerPicker build: ${widget.control.id}"); - int value = widget.control.attrInt("value", 0)!; - Duration initialTimerDuration = Duration(seconds: value); - Widget picker = CupertinoTimerPicker( - mode: parseCupertinoTimerPickerMode( - widget.control.attrString("mode"), CupertinoTimerPickerMode.hms)!, - initialTimerDuration: initialTimerDuration, - minuteInterval: widget.control.attrInt("minuteInterval", 1)!, - secondInterval: widget.control.attrInt("secondInterval", 1)!, - itemExtent: widget.control.attrDouble("itemExtent", 32.0)!, - alignment: parseAlignment(widget.control, "alignment", Alignment.center)!, - backgroundColor: widget.control.attrColor("bgColor", context), - onTimerDurationChanged: (Duration d) { - widget.backend.updateControlState( - widget.control.id, {"value": d.inSeconds.toString()}); - widget.backend.triggerControlEvent( - widget.control.id, "change", d.inSeconds.toString()); + mode: widget.control + .getCupertinoTimerPickerMode("mode", CupertinoTimerPickerMode.hms)!, + initialTimerDuration: widget.control + .getDuration("value", Duration.zero, DurationUnit.seconds)!, + minuteInterval: widget.control.getInt("minute_interval", 1)!, + secondInterval: widget.control.getInt("second_interval", 1)!, + itemExtent: widget.control.getDouble("item_extent", 32.0)!, + alignment: widget.control.getAlignment("alignment", Alignment.center)!, + backgroundColor: widget.control.getColor("bgcolor", context), + onTimerDurationChanged: (duration) { + // preserve (original) value's type + final d = + widget.control.get("value") is int ? duration.inSeconds : duration; + widget.control.updateProperties({"value": d}); + widget.control.triggerEvent("change", d); }, ); - return constrainedControl(context, picker, widget.parent, widget.control); + return ConstrainedControl(control: widget.control, child: picker); } } diff --git a/packages/flet/lib/src/controls/datatable.dart b/packages/flet/lib/src/controls/datatable.dart index 2919888f3..bbce9084a 100644 --- a/packages/flet/lib/src/controls/datatable.dart +++ b/packages/flet/lib/src/controls/datatable.dart @@ -1,207 +1,132 @@ -import 'dart:convert'; - +import 'package:flet/src/utils/events.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/alignment.dart'; import '../utils/borders.dart'; import '../utils/colors.dart'; import '../utils/gradient.dart'; -import '../utils/others.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; import '../utils/text.dart'; -import 'create_control.dart'; -import 'flet_store_mixin.dart'; +import '../utils/tooltip.dart'; +import 'base_controls.dart'; -class DataTableControl extends StatefulWidget { - final Control? parent; +class DataTableControl extends StatelessWidget { final Control control; - final List children; - final bool parentDisabled; - final FletControlBackend backend; - const DataTableControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.backend}); + const DataTableControl({super.key, required this.control}); @override - State createState() => _DataTableControlState(); -} - -class _DataTableControlState extends State - with FletStoreMixin { - @override Widget build(BuildContext context) { - debugPrint("DataTableControl build: ${widget.control.id}"); - - bool tableDisabled = widget.control.isDisabled || widget.parentDisabled; + debugPrint("DataTableControl build: ${control.id}"); - var datatable = - withControls(widget.children.where((c) => c.isVisible).map((c) => c.id), - (content, viewModel) { - var bgColor = widget.control.attrString("bgColor"); - var border = parseBorder(Theme.of(context), widget.control, "border"); - var borderRadius = parseBorderRadius(widget.control, "borderRadius"); - var gradient = - parseGradient(Theme.of(context), widget.control, "gradient"); - var horizontalLines = - parseBorderSide(Theme.of(context), widget.control, "horizontalLines"); - var verticalLines = - parseBorderSide(Theme.of(context), widget.control, "verticalLines"); - var defaultDecoration = - Theme.of(context).dataTableTheme.decoration ?? const BoxDecoration(); + var theme = Theme.of(context); + var bgcolor = control.getString("bgcolor"); + var border = control.getBorder("border", theme); + var borderRadius = control.getBorderRadius("border_radius"); + var gradient = control.getGradient("gradient", theme); + var horizontalLines = control.getBorderSide("horizontal_lLines", theme); + var verticalLines = control.getBorderSide("vertical_lines", theme); + var defaultDecoration = + theme.dataTableTheme.decoration ?? const BoxDecoration(); - BoxDecoration? decoration; - if (bgColor != null || - border != null || - borderRadius != null || - gradient != null) { - decoration = (defaultDecoration as BoxDecoration).copyWith( - color: parseColor(Theme.of(context), bgColor), - border: border, - borderRadius: borderRadius, - gradient: gradient); - } + BoxDecoration? decoration; + if (bgcolor != null || + border != null || + borderRadius != null || + gradient != null) { + decoration = (defaultDecoration as BoxDecoration).copyWith( + color: parseColor(bgcolor, theme), + border: border, + borderRadius: borderRadius, + gradient: gradient); + } - TableBorder? tableBorder; - if (horizontalLines != null || verticalLines != null) { - tableBorder = TableBorder( - horizontalInside: horizontalLines ?? BorderSide.none, - verticalInside: verticalLines ?? BorderSide.none); - } - - Clip clipBehavior = - parseClip(widget.control.attrString("clipBehavior"), Clip.none)!; - - return DataTable( - decoration: decoration, - border: tableBorder, - clipBehavior: clipBehavior, - checkboxHorizontalMargin: - widget.control.attrDouble("checkboxHorizontalMargin"), - columnSpacing: widget.control.attrDouble("columnSpacing"), - dataRowColor: parseWidgetStateColor( - Theme.of(context), widget.control, "dataRowColor"), - dataRowMinHeight: widget.control.attrDouble("dataRowMinHeight"), - dataRowMaxHeight: widget.control.attrDouble("dataRowMaxHeight"), - dataTextStyle: parseTextStyle( - Theme.of(context), widget.control, "dataTextStyle"), - headingRowColor: parseWidgetStateColor( - Theme.of(context), widget.control, "headingRowColor"), - headingRowHeight: widget.control.attrDouble("headingRowHeight"), - headingTextStyle: parseTextStyle( - Theme.of(context), widget.control, "headingTextStyle"), - dividerThickness: widget.control.attrDouble("dividerThickness"), - horizontalMargin: widget.control.attrDouble("horizontalMargin"), - showBottomBorder: widget.control.attrBool("showBottomBorder", false)!, - showCheckboxColumn: - widget.control.attrBool("showCheckboxColumn", false)!, - sortAscending: widget.control.attrBool("sortAscending", false)!, - sortColumnIndex: widget.control.attrInt("sortColumnIndex"), - onSelectAll: widget.control.attrBool("onSelectAll", false)! - ? (bool? selected) { - widget.backend.triggerControlEvent( - widget.control.id, "select_all", selected?.toString()); - } + var datatable = DataTable( + decoration: decoration, + border: (horizontalLines != null || verticalLines != null) + ? TableBorder( + horizontalInside: horizontalLines ?? BorderSide.none, + verticalInside: verticalLines ?? BorderSide.none) + : null, + clipBehavior: parseClip(control.getString("clip_behavior"), Clip.none)!, + checkboxHorizontalMargin: control.getDouble("checkbox_horizontal_margin"), + columnSpacing: control.getDouble("column_spacing"), + dataRowColor: control.getWidgetStateColor("data_row_color", theme), + dataRowMinHeight: control.getDouble("data_row_min_height"), + dataRowMaxHeight: control.getDouble("data_row_max_height"), + dataTextStyle: control.getTextStyle("data_text_style", theme), + headingRowColor: control.getWidgetStateColor("heading_row_color", theme), + headingRowHeight: control.getDouble("heading_row_height"), + headingTextStyle: control.getTextStyle("heading_text_style", theme), + dividerThickness: control.getDouble("divider_thickness"), + horizontalMargin: control.getDouble("horizontal_margin"), + showBottomBorder: control.getBool("show_bottom_border", false)!, + showCheckboxColumn: control.getBool("show_checkbox_column", false)!, + sortAscending: control.getBool("sort_ascending", false)!, + sortColumnIndex: control.getInt("sort_column_index"), + onSelectAll: control.getBool("on_select_all", false)! + ? (bool? selected) => control.triggerEvent("select_all", selected) + : null, + columns: control.children("columns").map((column) { + column.notifyParent = true; + var tooltip = + parseTooltip(column.get("tooltip"), context, const Placeholder()); + return DataColumn( + numeric: column.getBool("numeric", false)!, + tooltip: tooltip?.message, + headingRowAlignment: + column.getMainAxisAlignment("heading_row_alignment"), + mouseCursor: WidgetStateMouseCursor.clickable, + onSort: column.getBool("on_sort", false)! + ? (columnIndex, ascending) => column + .triggerEvent("sort", {"ci": columnIndex, "asc": ascending}) + : null, + label: column.buildTextOrWidget("label")!, + ); + }).toList(), + rows: control.children("rows").map((row) { + row.notifyParent = true; + return DataRow( + key: ValueKey(row.id), + selected: row.getBool("selected", false)!, + color: row.getWidgetStateColor("color", theme), + onSelectChanged: row.getBool("on_select_change", false)! + ? (selected) => row.triggerEvent("select_change", selected) + : null, + onLongPress: row.getBool("on_long_press", false)! + ? () => row.triggerEvent("long_press") : null, - columns: viewModel.controlViews - .where( - (c) => c.control.type == "datacolumn" && c.control.isVisible) - .map((column) { - var labelCtrls = - column.children.where((c) => c.name == "label" && c.isVisible); - return DataColumn( - numeric: column.control.attrBool("numeric", false)!, - tooltip: column.control.attrString("tooltip"), - headingRowAlignment: parseMainAxisAlignment( - column.control.attrString("headingRowAlignment")), - mouseCursor: WidgetStateMouseCursor.clickable, - onSort: column.control.attrBool("onSort", false)! - ? (columnIndex, ascending) { - widget.backend.triggerControlEvent( - column.control.id, - "sort", - json.encode({"i": columnIndex, "a": ascending})); - } - : null, - label: createControl(column.control, labelCtrls.first.id, - column.control.isDisabled || tableDisabled)); + cells: row.children("cells").map((cell) { + cell.notifyParent = true; + return DataCell( + cell.buildTextOrWidget("content")!, + placeholder: cell.getBool("placeholder", false)!, + showEditIcon: cell.getBool("show_edit_icon", false)!, + onDoubleTap: cell.getBool("on_double_tap", false)! + ? () => cell.triggerEvent("double_tap") + : null, + onLongPress: cell.getBool("on_long_press", false)! + ? () => cell.triggerEvent("long_press") + : null, + onTap: cell.getBool("on_tap", false)! + ? () => cell.triggerEvent("tap") + : null, + onTapCancel: cell.getBool("on_tap_cancel", false)! + ? () => cell.triggerEvent("tap_cancel") + : null, + onTapDown: cell.getBool("on_tap_down", false)! + ? (TapDownDetails details) => + cell.triggerEvent("tap_down", details.toMap()) + : null, + ); }).toList(), - rows: viewModel.controlViews - .where((c) => c.control.type == "datarow" && c.control.isVisible) - .map((row) { - return DataRow( - key: ValueKey(row.control.id), - selected: row.control.attrBool("selected", false)!, - color: parseWidgetStateColor( - Theme.of(context), row.control, "color"), - onSelectChanged: row.control.attrBool("onSelectChanged", false)! - ? (selected) { - widget.backend.triggerControlEvent(row.control.id, - "select_changed", selected?.toString()); - } - : null, - onLongPress: row.control.attrBool("onLongPress", false)! - ? () { - widget.backend - .triggerControlEvent(row.control.id, "long_press"); - } - : null, - cells: row.children - .where((c) => c.type == "datacell" && c.isVisible) - .map((cell) => DataCell( - createControl(row.control, cell.childIds.first, - row.control.isDisabled || tableDisabled), - placeholder: cell.attrBool("placeholder", false)!, - showEditIcon: cell.attrBool("showEditIcon", false)!, - onDoubleTap: cell.attrBool("onDoubleTap", false)! - ? () { - widget.backend.triggerControlEvent( - cell.id, "double_tap"); - } - : null, - onLongPress: cell.attrBool("onLongPress", false)! - ? () { - widget.backend.triggerControlEvent( - cell.id, "long_press"); - } - : null, - onTap: cell.attrBool("onTap", false)! - ? () { - widget.backend - .triggerControlEvent(cell.id, "tap"); - } - : null, - onTapCancel: cell.attrBool("onTapCancel", false)! - ? () { - widget.backend.triggerControlEvent( - cell.id, "tap_cancel"); - } - : null, - onTapDown: cell.attrBool("onTapDown", false)! - ? (details) { - widget.backend.triggerControlEvent( - cell.id, - "tap_down", - json.encode({ - "kind": details.kind?.name, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - })); - } - : null, - )) - .toList()); - }).toList()); - }); + ); + }).toList(), + ); - return constrainedControl( - context, datatable, widget.parent, widget.control); + return ConstrainedControl(control: control, child: datatable); } } diff --git a/packages/flet/lib/src/controls/date_picker.dart b/packages/flet/lib/src/controls/date_picker.dart index efb86b04c..e5ea274a1 100644 --- a/packages/flet/lib/src/controls/date_picker.dart +++ b/packages/flet/lib/src/controls/date_picker.dart @@ -1,23 +1,17 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; +import '../utils/colors.dart'; import '../utils/form_field.dart'; import '../utils/icons.dart'; -import '../utils/others.dart'; +import '../utils/numbers.dart'; +import '../utils/time.dart'; class DatePickerControl extends StatefulWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final FletControlBackend backend; - const DatePickerControl( - {super.key, - this.parent, - required this.control, - required this.parentDisabled, - required this.backend}); + DatePickerControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _DatePickerControlState(); @@ -28,57 +22,47 @@ class _DatePickerControlState extends State { Widget build(BuildContext context) { debugPrint("DatePicker build: ${widget.control.id}"); - bool lastOpen = widget.control.state["open"] ?? false; + bool lastOpen = widget.control.getBool("_open", false)!; - var open = widget.control.attrBool("open", false)!; - DateTime? value = widget.control.attrDateTime("value"); - DateTime? currentDate = widget.control.attrDateTime("currentDate"); - IconData? switchToCalendarEntryModeIcon = parseIcon( - widget.control.attrString("switchToCalendarEntryModeIcon", "")!); - IconData? switchToInputEntryModeIcon = - parseIcon(widget.control.attrString("switchToInputEntryModeIcon")); + var open = widget.control.getBool("open", false)!; + var value = widget.control.getDateTime("value"); + var currentDate = widget.control.getDateTime("current_date"); + var switchToCalendarEntryModeIcon = + parseIcon(widget.control.getString("switch_to_calendar_icon", "")!); + var switchToInputEntryModeIcon = + parseIcon(widget.control.getString("switch_to_input_icon")); void onClosed(DateTime? dateValue) { - String stringValue; - String eventName; - if (dateValue == null) { - stringValue = - value?.toIso8601String() ?? currentDate?.toIso8601String() ?? ""; - eventName = "dismiss"; - } else { - stringValue = dateValue.toIso8601String(); - eventName = "change"; + widget.control.updateProperties({"_open": false}, python: false); + widget.control + .updateProperties({"value": dateValue ?? value, "open": false}); + if (dateValue != null) { + widget.control.triggerEvent("change", dateValue); } - widget.control.state["open"] = false; - widget.backend.updateControlState( - widget.control.id, {"value": stringValue, "open": "false"}); - widget.backend - .triggerControlEvent(widget.control.id, eventName, stringValue); + widget.control.triggerEvent("dismiss", dateValue == null); } Widget createSelectDateDialog() { Widget dialog = DatePickerDialog( initialDate: value ?? currentDate ?? DateTime.now(), - firstDate: widget.control.attrDateTime("firstDate", DateTime(1900))!, - lastDate: widget.control.attrDateTime("lastDate", DateTime(2050))!, + firstDate: widget.control.getDateTime("first_date", DateTime(1900))!, + lastDate: widget.control.getDateTime("last_date", DateTime(2050))!, currentDate: currentDate ?? DateTime.now(), - helpText: widget.control.attrString("helpText"), - cancelText: widget.control.attrString("cancelText"), - confirmText: widget.control.attrString("confirmText"), - errorFormatText: widget.control.attrString("errorFormatText"), - errorInvalidText: widget.control.attrString("errorInvalidText"), + helpText: widget.control.getString("help_text"), + cancelText: widget.control.getString("cancel_text"), + confirmText: widget.control.getString("confirm_text"), + errorFormatText: widget.control.getString("error_format_text"), + errorInvalidText: widget.control.getString("error_invalid_text"), keyboardType: parseTextInputType( - widget.control.attrString("keyboardType"), TextInputType.text)!, - initialCalendarMode: parseDatePickerMode( - widget.control.attrString("datePickerMode"), DatePickerMode.day)!, - initialEntryMode: parseDatePickerEntryMode( - widget.control.attrString("datePickerEntryMode"), - DatePickerEntryMode.calendar)!, - fieldHintText: widget.control.attrString("fieldHintText"), - fieldLabelText: widget.control.attrString("fieldLabelText"), + widget.control.getString("keyboard_type"), TextInputType.text)!, + initialCalendarMode: widget.control + .getDatePickerMode("date_picker_mode", DatePickerMode.day)!, + initialEntryMode: widget.control.getDatePickerEntryMode( + "date_picker_entry_mode", DatePickerEntryMode.calendar)!, + fieldHintText: widget.control.getString("field_hint_text"), + fieldLabelText: widget.control.getString("field_label_text"), onDatePickerModeChange: (DatePickerEntryMode mode) { - widget.backend.triggerControlEvent( - widget.control.id, "entryModeChange", mode.name); + widget.control.triggerEvent("entry_mode_change", mode.name); }, switchToCalendarEntryModeIcon: switchToCalendarEntryModeIcon != null ? Icon(switchToCalendarEntryModeIcon) @@ -92,11 +76,12 @@ class _DatePickerControlState extends State { } if (open && (open != lastOpen)) { - widget.control.state["open"] = open; + widget.control.updateProperties({"_open": open}, python: false); WidgetsBinding.instance.addPostFrameCallback((_) { showDialog( - barrierColor: widget.control.attrColor("barrierColor", context), + barrierDismissible: !widget.control.getBool("modal", false)!, + barrierColor: widget.control.getColor("barrier_color", context), useRootNavigator: false, context: context, builder: (context) => createSelectDateDialog()).then((result) { diff --git a/packages/flet/lib/src/controls/dismissible.dart b/packages/flet/lib/src/controls/dismissible.dart index c630541e3..8f7acee91 100644 --- a/packages/flet/lib/src/controls/dismissible.dart +++ b/packages/flet/lib/src/controls/dismissible.dart @@ -1,30 +1,20 @@ import 'dart:async'; -import 'dart:convert'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/dismissible.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/numbers.dart'; +import '../utils/time.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; class DismissibleControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const DismissibleControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + DismissibleControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _DismissibleControlState(); @@ -32,111 +22,90 @@ class DismissibleControl extends StatefulWidget { class _DismissibleControlState extends State { @override - Widget build(BuildContext context) { - debugPrint("Dismissible build: ${widget.control.id}"); + void initState() { + super.initState(); + widget.control.addInvokeMethodListener(_invokeMethod); + } - bool disabled = widget.control.isDisabled || widget.parentDisabled; - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); + @override + void dispose() { + widget.control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } - if (contentCtrls.isEmpty) { - return const ErrorControl( - "Dismissible.content must be provided and visible"); + Future _invokeMethod(String name, dynamic args) async { + debugPrint("Dismissible.$name($args)"); + switch (name) { + case "confirm_dismiss": + widget.control.properties + .remove("_completer") + ?.complete(args["dismiss"]); + default: + throw Exception("Unknown Dismissible method: $name"); } + } - var backgroundCtrls = - widget.children.where((c) => c.name == "background" && c.isVisible); - - var secondaryBackgroundCtrls = widget.children - .where((c) => c.name == "secondaryBackground" && c.isVisible); - - var dismissThresholds = - parseDismissThresholds(widget.control, "dismissThresholds"); - - DismissDirection direction = parseDismissDirection( - widget.control.attrString("dismissDirection"), - DismissDirection.horizontal)!; - - widget.backend.subscribeMethods(widget.control.id, - (methodName, args) async { - debugPrint("Dismissible.onMethod(${widget.control.id})"); - if (methodName == "confirm_dismiss") { - widget.control.state["confirm_dismiss"] - ?.complete(bool.tryParse(args["dismiss"] ?? "")); - widget.backend.unsubscribeMethods(widget.control.id); - } + @override + Widget build(BuildContext context) { + debugPrint("Dismissible build: ${widget.control.id}"); + var content = widget.control.buildWidget("content"); - return null; - }); + if (content == null) { + return const ErrorControl("Dismissible.content must be visible"); + } - return constrainedControl( - context, - Dismissible( - key: ValueKey(widget.control.id), - direction: direction, - background: backgroundCtrls.isNotEmpty - ? createControl( - widget.control, backgroundCtrls.first.id, disabled, - parentAdaptive: adaptive) - : Container(color: Colors.transparent), - secondaryBackground: secondaryBackgroundCtrls.isNotEmpty - ? createControl( - widget.control, secondaryBackgroundCtrls.first.id, disabled, - parentAdaptive: adaptive) - : Container(color: Colors.transparent), - onDismissed: widget.control.attrBool("onDismiss", false)! - ? (DismissDirection direction) { - widget.backend.triggerControlEvent( - widget.control.id, "dismiss", direction.name); - } - : null, - onResize: widget.control.attrBool("onResize", false)! - ? () { - widget.backend - .triggerControlEvent(widget.control.id, "resize"); - } - : null, - onUpdate: widget.control.attrBool("onUpdate", false)! - ? (DismissUpdateDetails details) { - widget.backend.triggerControlEvent( - widget.control.id, - "update", - json.encode(DismissibleUpdateEvent( - direction: details.direction.name, - previousReached: details.previousReached, - progress: details.progress, - reached: details.reached) - .toJson())); - } - : null, - confirmDismiss: widget.control.attrBool("onConfirmDismiss", false)! - ? (DismissDirection direction) { - debugPrint( - "Dismissible.confirmDismiss(${widget.control.id})"); - var completer = Completer(); - widget.control.state["confirm_dismiss"] = completer; - widget.backend.triggerControlEvent( - widget.control.id, "confirm_dismiss", direction.name); - return completer.future.timeout( - const Duration(minutes: 5), - onTimeout: () => false, - ); - } - : null, - movementDuration: Duration( - milliseconds: widget.control.attrInt("duration", 200)!), - resizeDuration: Duration( - milliseconds: widget.control.attrInt("resizeDuration", 300)!), - crossAxisEndOffset: - widget.control.attrDouble("crossAxisEndOffset", 0.0)!, - dismissThresholds: dismissThresholds ?? {}, - child: createControl( - widget.control, contentCtrls.first.id, disabled, - parentAdaptive: adaptive)), - widget.parent, - widget.control); + final dismissible = Dismissible( + key: ValueKey(widget.control.id), + direction: widget.control.getDismissDirection( + "dismiss_direction", DismissDirection.horizontal)!, + background: widget.control.buildWidget("background") ?? + Container(color: Colors.transparent), + secondaryBackground: + widget.control.buildWidget("secondary_background") ?? + Container(color: Colors.transparent), + onDismissed: widget.control.getBool("on_dismiss", false)! + ? (DismissDirection direction) => widget.control + .triggerEvent("dismiss", {"direction": direction.name}) + : null, + onResize: widget.control.getBool("on_resize", false)! + ? () => widget.control.triggerEvent("resize") + : null, + onUpdate: widget.control.getBool("on_update", false)! + ? (DismissUpdateDetails details) { + widget.control.triggerEvent( + "update", + DismissibleUpdateEvent( + direction: details.direction.name, + previousReached: details.previousReached, + progress: details.progress, + reached: details.reached) + .toMap()); + } + : null, + confirmDismiss: widget.control.getBool("on_confirm_dismiss", false)! + ? (DismissDirection direction) { + var completer = Completer(); + widget.control + .updateProperties({"_completer": completer}, python: false); + widget.control.triggerEvent( + "confirm_dismiss", {"direction": direction.name}); + return completer.future.timeout( + const Duration(minutes: 5), + onTimeout: () => false, + ); + } + : null, + movementDuration: widget.control + .getDuration("duration", const Duration(milliseconds: 200))!, + resizeDuration: widget.control + .getDuration("duration", const Duration(milliseconds: 300))!, + crossAxisEndOffset: + widget.control.getDouble("cross_axis_end_offset", 0.0)!, + dismissThresholds: widget.control + .getDismissThresholds("dismiss_thresholds", const {})!, + child: content); + + return ConstrainedControl(control: widget.control, child: dismissible); } } @@ -152,7 +121,7 @@ class DismissibleUpdateEvent { required this.previousReached, required this.reached}); - Map toJson() => { + Map toMap() => { 'direction': direction, 'progress': progress, 'reached': reached, diff --git a/packages/flet/lib/src/controls/divider.dart b/packages/flet/lib/src/controls/divider.dart index 1459c8b02..101194664 100644 --- a/packages/flet/lib/src/controls/divider.dart +++ b/packages/flet/lib/src/controls/divider.dart @@ -1,26 +1,27 @@ import 'package:flutter/material.dart'; import '../models/control.dart'; -import 'create_control.dart'; +import '../utils/colors.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; class DividerControl extends StatelessWidget { - final Control? parent; final Control control; - const DividerControl( - {super.key, required this.parent, required this.control}); + const DividerControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("Divider build: ${control.id}"); var divider = Divider( - height: control.attrDouble("height"), - thickness: control.attrDouble("thickness"), - color: control.attrColor("color", context), - indent: control.attrDouble("leadingIndent"), - endIndent: control.attrDouble("trailingIndent"), + height: control.getDouble("height"), + thickness: control.getDouble("thickness"), + color: control.getColor("color", context), + indent: control.getDouble("leading_indent"), + endIndent: control.getDouble("trailing_indent"), ); - return baseControl(context, divider, parent, control); + + return BaseControl(control: control, child: divider); } } diff --git a/packages/flet/lib/src/controls/drag_target.dart b/packages/flet/lib/src/controls/drag_target.dart index 2619a4af4..f34246d29 100644 --- a/packages/flet/lib/src/controls/drag_target.dart +++ b/packages/flet/lib/src/controls/drag_target.dart @@ -1,14 +1,13 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; +import 'draggable.dart'; class DragTargetEvent { - final String srcId; + final int srcId; final double x; final double y; @@ -18,102 +17,60 @@ class DragTargetEvent { required this.y, }); - Map toJson() => { - 'src_id': srcId, - 'x': x, - 'y': y, - }; + Map toMap() => + {'src_id': srcId, 'x': x, 'y': y}; } class DragTargetControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const DragTargetControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const DragTargetControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("DragTarget build: ${control.id}"); - var group = control.attrString("group", ""); - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - bool disabled = control.isDisabled || parentDisabled; - - Widget? child = contentCtrls.isNotEmpty - ? createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: parentAdaptive) - : null; + var group = control.getString("group", "default")!; + var content = control.buildWidget("content"); - if (child == null) { - return const ErrorControl( - "DragTarget.content must be provided and visible"); + if (content == null) { + return const ErrorControl("DragTarget.content must be visible"); } - return DragTarget( + return DragTarget( builder: ( BuildContext context, List accepted, List rejected, ) { - debugPrint( - "DragTarget.builder ${control.id}: accepted=${accepted.length}, rejected=${rejected.length}"); - return child; + return content; }, - onMove: (details) { - var data = details.data; - debugPrint("DragTarget.onMove ${control.id}: $data"); - var jd = json.decode(data); - var srcId = jd["id"] as String; - backend.triggerControlEvent( - control.id, + onMove: (DragTargetDetails details) { + control.triggerEvent( "move", - json.encode(DragTargetEvent( - srcId: srcId, x: details.offset.dx, y: details.offset.dy) - .toJson())); + DragTargetEvent( + srcId: details.data.id, + x: details.offset.dx, + y: details.offset.dy) + .toMap()); }, - onWillAcceptWithDetails: (details) { - var data = details.data; - debugPrint("DragTarget.onWillAcceptWithDetails ${control.id}: $data"); - String srcGroup = ""; - var jd = json.decode(data); - srcGroup = jd["group"] as String; - var groupsEqual = srcGroup == group; - backend.triggerControlEvent( - control.id, "will_accept", groupsEqual.toString()); - return groupsEqual; + onWillAcceptWithDetails: (DragTargetDetails details) { + var groupMatch = details.data.group == group; + control.triggerEvent( + "will_accept", {"accept": groupMatch, "src_id": details.data.id}); + return groupMatch; }, - onAcceptWithDetails: (details) { - var data = details.data; - debugPrint("DragTarget.onAcceptWithDetails ${control.id}: $data"); - var jd = json.decode(data); - var srcId = jd["id"] as String; - backend.triggerControlEvent( - control.id, + onAcceptWithDetails: (DragTargetDetails details) { + control.triggerEvent( "accept", - json.encode(DragTargetEvent( - srcId: srcId, x: details.offset.dx, y: details.offset.dy) - .toJson())); + DragTargetEvent( + srcId: details.data.id, + x: details.offset.dx, + y: details.offset.dy) + .toMap()); }, - onLeave: (data) { - debugPrint("DragTarget.onLeave ${control.id}: $data"); - String srcId = ""; - if (data != null) { - var jd = json.decode(data); - srcId = jd["id"] as String; - } - backend.triggerControlEvent(control.id, "leave", srcId); + onLeave: (DraggableData? data) { + control.triggerEvent("leave", {"src_id", data?.id}); }, ); } diff --git a/packages/flet/lib/src/controls/draggable.dart b/packages/flet/lib/src/controls/draggable.dart index c2d763afc..46f7ea89a 100644 --- a/packages/flet/lib/src/controls/draggable.dart +++ b/packages/flet/lib/src/controls/draggable.dart @@ -1,85 +1,49 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; + +class DraggableData { + final int id; + final String group; + + DraggableData({required this.id, required this.group}); +} class DraggableControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const DraggableControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const DraggableControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("DragTarget build: ${control.id}"); - var adaptive = control.isAdaptive ?? parentAdaptive; + var group = control.getString("group", "default")!; + var content = control.buildWidget("content"); - var group = control.attrString("group", ""); - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - var contentWhenDraggingCtrls = - children.where((c) => c.name == "content_when_dragging" && c.isVisible); - var contentFeedbackCtrls = - children.where((c) => c.name == "content_feedback" && c.isVisible); - bool disabled = control.isDisabled || parentDisabled; - - Widget? child = contentCtrls.isNotEmpty - ? createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null; - - Widget? childFeedback = contentFeedbackCtrls.isNotEmpty - ? createControl(control, contentFeedbackCtrls.first.id, disabled, - parentAdaptive: adaptive) - : Opacity(opacity: 0.5, child: child); - - if (child == null) { - return const ErrorControl( - "Draggable.content must be provided and visible"); + if (content == null) { + return const ErrorControl("Draggable.content must be visible"); } - var data = json.encode({"id": control.id, "group": group}); - - return Draggable( - data: data, - axis: parseAxis(control.attrString("axis")), - affinity: parseAxis(control.attrString("affinity")), - maxSimultaneousDrags: control.attrInt("maxSimultaneousDrags"), - childWhenDragging: contentWhenDraggingCtrls.isNotEmpty - ? createControl(control, contentWhenDraggingCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null, + return Draggable( + data: DraggableData(id: control.id, group: group), + axis: control.getAxis("axis"), + affinity: control.getAxis("affinity"), + maxSimultaneousDrags: control.getInt("max_simultaneous_drags"), + onDragStarted: () => control.triggerEvent("drag_start"), + onDragCompleted: () => control.triggerEvent("drag_complete", group), + childWhenDragging: control.buildWidget("content_when_dragging"), feedback: MouseRegion( cursor: SystemMouseCursors.grabbing, - child: childFeedback, + child: control.buildWidget("content_feedback") ?? + Opacity(opacity: 0.5, child: content), ), - onDragStarted: () { - debugPrint("Draggable.onDragStarted ${control.id}"); - backend.triggerControlEvent(control.id, "dragStart"); - }, - onDragCompleted: () { - debugPrint("Draggable.onDragCompleted ${control.id}"); - backend.triggerControlEvent(control.id, "dragComplete"); - }, child: MouseRegion( cursor: SystemMouseCursors.grab, - child: child, + child: content, ), ); } diff --git a/packages/flet/lib/src/controls/dropdown.dart b/packages/flet/lib/src/controls/dropdown.dart index 768852063..2ac1d8130 100644 --- a/packages/flet/lib/src/controls/dropdown.dart +++ b/packages/flet/lib/src/controls/dropdown.dart @@ -1,355 +1,227 @@ +import 'package:flet/flet.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; -import '../flet_control_backend.dart'; -import '../models/control.dart'; -import '../models/control_view_model.dart'; -import '../utils/borders.dart'; -import '../utils/buttons.dart'; -import '../utils/colors.dart'; -import '../utils/edge_insets.dart'; -import '../utils/form_field.dart'; -import '../utils/icons.dart'; -import '../utils/numbers.dart'; -import '../utils/text.dart'; -import '../utils/textfield.dart'; -import 'create_control.dart'; -import 'flet_store_mixin.dart'; -import 'textfield.dart'; - class DropdownControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const DropdownControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + DropdownControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _DropdownControlState(); } -class _DropdownControlState extends State with FletStoreMixin { - String? _value; +class _DropdownControlState extends State { late final FocusNode _focusNode; - String? _lastFocusValue; @override void initState() { super.initState(); _focusNode = FocusNode(); _focusNode.addListener(_onFocusChange); + widget.control.addInvokeMethodListener(_invokeMethod); } void _onFocusChange() { - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); } @override void dispose() { _focusNode.removeListener(_onFocusChange); _focusNode.dispose(); + widget.control.removeInvokeMethodListener(_invokeMethod); super.dispose(); } + Future _invokeMethod(String name, dynamic args) async { + debugPrint("Dropdown.$name($args)"); + switch (name) { + case "focus": + _focusNode.requestFocus(); + default: + throw Exception("Unknown Dropdown method: $name"); + } + } + @override Widget build(BuildContext context) { debugPrint("DropdownMenu build: ${widget.control.id}"); - return withControls(widget.control.childIds, (context, itemsView) { - debugPrint("DropdownMenuFletControlState build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - bool editable = widget.control.attrBool("editable", false)!; - bool autofocus = widget.control.attrBool("autofocus", false)!; - var textSize = widget.control.attrDouble("textSize"); - var label = widget.control.attrString("label"); - var trailingIconCtrl = widget.children - .where((c) => c.name == "trailing_icon" && c.isVisible); - var trailingIconStr = - parseIcon(widget.control.attrString("trailingIcon")); - - var leadingIconCtrl = - widget.children.where((c) => c.name == "leading_icon" && c.isVisible); - var leadingIconStr = parseIcon(widget.control.attrString("leadingIcon")); - - var selectIconCtrl = - widget.children.where((c) => c.name == "select_icon" && c.isVisible); - var selectIconStr = parseIcon(widget.control.attrString("selectIcon")); - - var selectedTrailingIconCtrl = widget.children - .where((c) => c.name == "selected_trailing_icon" && c.isVisible); - var selectedTrailingIconStr = - parseIcon(widget.control.attrString("selectedTrailingIcon")); - var prefixIconCtrl = - widget.children.where((c) => c.name == "prefix_icon" && c.isVisible); - var prefixIconStr = parseIcon(widget.control.attrString("prefixIcon")); - var labelCtrl = - widget.children.where((c) => c.name == "label" && c.isVisible); - var color = widget.control.attrColor("color", context); - - TextAlign textAlign = parseTextAlign( - widget.control.attrString("textAlign"), TextAlign.start)!; - - var fillColor = widget.control.attrColor("fillColor", context); - var borderColor = widget.control.attrColor("borderColor", context); - - var borderRadius = parseBorderRadius(widget.control, "borderRadius"); - var focusedBorderColor = - widget.control.attrColor("focusedBorderColor", context); - var borderWidth = widget.control.attrDouble("borderWidth"); - var focusedBorderWidth = widget.control.attrDouble("focusedBorderWidth"); - var menuWidth = widget.control.attrDouble("menuWidth") ?? double.infinity; - FormFieldInputBorder inputBorder = parseFormFieldInputBorder( - widget.control.attrString("border"), - FormFieldInputBorder.outline, - )!; - - InputBorder? border; - - if (inputBorder == FormFieldInputBorder.underline) { - border = UnderlineInputBorder( - borderSide: BorderSide( - color: borderColor ?? const Color(0xFF000000), - width: borderWidth ?? 1.0)); - } else if (inputBorder == FormFieldInputBorder.none) { - border = InputBorder.none; - } else if (inputBorder == FormFieldInputBorder.outline || - borderRadius != null || - borderColor != null || - borderWidth != null) { - border = OutlineInputBorder( - borderSide: BorderSide( - color: borderColor ?? const Color(0xFF000000), - width: borderWidth ?? 1.0)); - if (borderRadius != null) { - border = (border as OutlineInputBorder) - .copyWith(borderRadius: borderRadius); - } - if (borderColor != null || borderWidth != null) { - border = (border as OutlineInputBorder).copyWith( - borderSide: borderWidth == 0 - ? BorderSide.none - : BorderSide( - color: borderColor ?? - Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.38), - width: borderWidth ?? 1.0)); - } + var theme = Theme.of(context); + bool editable = widget.control.getBool("editable", false)!; + bool autofocus = widget.control.getBool("autofocus", false)!; + var textSize = widget.control.getDouble("text_size"); + var color = widget.control.getColor("color", context); + + TextAlign textAlign = + widget.control.getTextAlign("text_align", TextAlign.start)!; + + var fillColor = widget.control.getColor("fill_color", context); + var borderColor = widget.control.getColor("border_color", context); + + var borderRadius = widget.control.getBorderRadius("border_radius"); + var focusedBorderColor = + widget.control.getColor("focused_border_color", context); + var borderWidth = widget.control.getDouble("border_width"); + var focusedBorderWidth = widget.control.getDouble("focused_border_width"); + var menuWidth = widget.control.getDouble("menu_width", double.infinity)!; + + FormFieldInputBorder inputBorder = widget.control + .getFormFieldInputBorder("border", FormFieldInputBorder.outline)!; + + InputBorder? border; + + if (inputBorder == FormFieldInputBorder.underline) { + border = UnderlineInputBorder( + borderSide: BorderSide( + color: borderColor ?? const Color(0xFF000000), + width: borderWidth ?? 1.0)); + } else if (inputBorder == FormFieldInputBorder.none) { + border = InputBorder.none; + } else if (inputBorder == FormFieldInputBorder.outline || + borderRadius != null || + borderColor != null || + borderWidth != null) { + border = OutlineInputBorder( + borderSide: BorderSide( + color: borderColor ?? const Color(0xFF000000), + width: borderWidth ?? 1.0)); + if (borderRadius != null) { + border = + (border as OutlineInputBorder).copyWith(borderRadius: borderRadius); } - - InputBorder? focusedBorder; - if (borderColor != null || - borderWidth != null || - focusedBorderColor != null || - focusedBorderWidth != null) { - focusedBorder = border?.copyWith( + if (borderColor != null || borderWidth != null) { + border = (border as OutlineInputBorder).copyWith( borderSide: borderWidth == 0 ? BorderSide.none : BorderSide( - color: focusedBorderColor ?? - borderColor ?? - Theme.of(context).colorScheme.primary, - width: focusedBorderWidth ?? borderWidth ?? 2.0)); + color: borderColor ?? + theme.colorScheme.onSurface.withOpacity(0.38), + width: borderWidth ?? 1.0)); } - - InputDecorationTheme inputDecorationTheme = InputDecorationTheme( - filled: widget.control.attrBool("filled", false)!, - fillColor: fillColor, - hintStyle: - parseTextStyle(Theme.of(context), widget.control, "hintStyle"), - errorStyle: - parseTextStyle(Theme.of(context), widget.control, "errorStyle"), - helperStyle: - parseTextStyle(Theme.of(context), widget.control, "helperStyle"), - border: border, - enabledBorder: border, - focusedBorder: focusedBorder, - isDense: widget.control.attrBool("dense") ?? false, - contentPadding: parseEdgeInsets(widget.control, "contentPadding"), + } + + InputBorder? focusedBorder; + if (borderColor != null || + borderWidth != null || + focusedBorderColor != null || + focusedBorderWidth != null) { + focusedBorder = border?.copyWith( + borderSide: borderWidth == 0 + ? BorderSide.none + : BorderSide( + color: focusedBorderColor ?? + borderColor ?? + theme.colorScheme.primary, + width: focusedBorderWidth ?? borderWidth ?? 2.0)); + } + + InputDecorationTheme inputDecorationTheme = InputDecorationTheme( + filled: widget.control.getBool("filled", false)!, + fillColor: fillColor, + hintStyle: widget.control.getTextStyle("hint_style", theme), + errorStyle: widget.control.getTextStyle("error_style", theme), + helperStyle: widget.control.getTextStyle("helper_style", theme), + border: border, + enabledBorder: border, + focusedBorder: focusedBorder, + isDense: widget.control.getBool("dense", false)!, + contentPadding: widget.control.getPadding("content_padding"), + ); + + TextStyle? textStyle = widget.control.getTextStyle("text_style", theme); + if (textSize != null || color != null) { + textStyle = (textStyle ?? const TextStyle()).copyWith( + fontSize: textSize, color: color ?? theme.colorScheme.onSurface); + } + + var items = widget.control + .children("options") + .map>((Control itemCtrl) { + bool itemDisabled = widget.control.disabled || itemCtrl.disabled; + ButtonStyle? style = itemCtrl.getButtonStyle("style", theme); + + return DropdownMenuEntry( + enabled: !itemDisabled, + value: itemCtrl.getString("key") ?? + itemCtrl.getString("text") ?? + itemCtrl.id.toString(), + label: itemCtrl.getString("text") ?? + itemCtrl.getString("key") ?? + itemCtrl.id.toString(), + labelWidget: itemCtrl.buildWidget("content"), + leadingIcon: itemCtrl.buildIconOrWidget("leading_icon"), + trailingIcon: itemCtrl.buildIconOrWidget("trailing_icon"), + style: style, ); - - TextStyle? textStyle = - parseTextStyle(Theme.of(context), widget.control, "textStyle"); - if (textSize != null || color != null) { - textStyle = (textStyle ?? const TextStyle()).copyWith( - fontSize: textSize, - color: color ?? Theme.of(context).colorScheme.onSurface); - } - - var items = itemsView.controlViews - .where((c) => - c.control.name == null && - c.control.type == "dropdownoption" && - c.control.isVisible) - .map>((ControlViewModel itemCtrlView) { - var itemCtrl = itemCtrlView.control; - bool itemDisabled = disabled || itemCtrl.isDisabled; - ButtonStyle? style = - parseButtonStyle(Theme.of(context), itemCtrl, "style"); - - var contentCtrls = itemCtrlView.children - .where((c) => c.name == "content" && c.isVisible); - var leadingIconCtrls = itemCtrlView.children - .where((c) => c.name == "leadingIcon" && c.isVisible); - var trailingIconCtrls = itemCtrlView.children - .where((c) => c.name == "trailingIcon" && c.isVisible); - - return DropdownMenuEntry( - enabled: !itemDisabled, - value: itemCtrl.attrs["key"] ?? itemCtrl.attrs["text"] ?? itemCtrl.id, - label: itemCtrl.attrs["text"] ?? itemCtrl.attrs["key"] ?? itemCtrl.id, - labelWidget: contentCtrls.isNotEmpty - ? createControl( - itemCtrlView.control, contentCtrls.first.id, itemDisabled) - : null, - leadingIcon: leadingIconCtrls.isNotEmpty - ? createControl( - itemCtrlView.control, leadingIconCtrls.first.id, itemDisabled) - : itemCtrlView.control.attrString("leadingIcon") != null - ? Icon( - parseIcon(itemCtrlView.control.attrString("leadingIcon"))) - : null, - trailingIcon: trailingIconCtrls.isNotEmpty - ? createControl(itemCtrlView.control, trailingIconCtrls.first.id, - itemDisabled) - : itemCtrlView.control.attrString("trailingIcon") != null - ? Icon(parseIcon( - itemCtrlView.control.attrString("trailingIcon"))) - : null, - style: style, - ); - }).toList(); - - String? value = widget.control.attrString("value"); - if (_value != value) { - _value = value; - } - - if (items.where((item) => item.value == value).isEmpty) { - _value = null; - } - - var focusValue = widget.control.attrString("focus"); - if (focusValue != null && focusValue != _lastFocusValue) { - _lastFocusValue = focusValue; - _focusNode.requestFocus(); - } - - TextCapitalization textCapitalization = parseTextCapitalization( - widget.control.attrString("capitalization"), - TextCapitalization.none)!; - - FilteringTextInputFormatter? inputFilter = - parseInputFilter(widget.control, "inputFilter"); - - List? inputFormatters = []; - // add non-null input formatters - if (inputFilter != null) { - inputFormatters.add(inputFilter); - } - if (textCapitalization != TextCapitalization.none) { - inputFormatters.add(TextCapitalizationFormatter(textCapitalization)); - } - - _focusNode.canRequestFocus = editable; - - Widget dropDown = DropdownMenu( - enabled: !disabled, - focusNode: _focusNode, - initialSelection: _value, - //controller: controller, - //requestFocusOnTap: editable, - enableFilter: widget.control.attrBool("enableFilter", false)!, - enableSearch: widget.control.attrBool("enableSearch", true)!, - menuHeight: widget.control.attrDouble("menuHeight"), - label: labelCtrl.isNotEmpty - ? createControl(widget.control, labelCtrl.first.id, disabled) - : label != null - ? Text(label, - style: parseTextStyle( - Theme.of(context), widget.control, "labelStyle")) - : null, - leadingIcon: leadingIconCtrl.isNotEmpty - ? createControl(widget.control, leadingIconCtrl.first.id, disabled) - : leadingIconStr != null - ? Icon(leadingIconStr) - : prefixIconCtrl.isNotEmpty - ? createControl( - widget.control, prefixIconCtrl.first.id, disabled) - : prefixIconStr != null - ? Icon(prefixIconStr) - : null, - trailingIcon: trailingIconCtrl.isNotEmpty - ? createControl(widget.control, trailingIconCtrl.first.id, disabled) - : trailingIconStr != null - ? Icon(trailingIconStr) - : selectIconCtrl.isNotEmpty - ? createControl( - widget.control, selectIconCtrl.first.id, disabled) - : selectIconStr != null - ? Icon(selectIconStr) - : null, - selectedTrailingIcon: selectedTrailingIconCtrl.isNotEmpty - ? createControl( - widget.control, selectedTrailingIconCtrl.first.id, disabled) - : selectedTrailingIconStr != null - ? Icon(selectedTrailingIconStr) - : null, - textStyle: textStyle, - textAlign: textAlign, - width: widget.control.attrDouble("width"), - errorText: widget.control.attrString("errorText"), - hintText: widget.control.attrString("hintText"), - helperText: widget.control.attrString("helperText"), - //inputFormatters: inputFormatters, - //expandedInsets: parseEdgeInsets(widget.control, "expandedInsets"), - expandedInsets: - widget.control.attrInt("expand") != null ? EdgeInsets.zero : null, - menuStyle: MenuStyle( - backgroundColor: parseWidgetStateColor( - Theme.of(context), widget.control, "bgcolor"), - elevation: parseWidgetStateDouble(widget.control, "elevation"), - fixedSize: WidgetStateProperty.all(Size.fromWidth(menuWidth)), - ), - - inputDecorationTheme: inputDecorationTheme, - onSelected: disabled - ? null - : (String? value) { - debugPrint("DropdownMenu selected value: $value"); - _value = value!; - widget.backend - .updateControlState(widget.control.id, {"value": value}); - widget.backend - .triggerControlEvent(widget.control.id, "change", value); - }, - dropdownMenuEntries: items, - ); - - var didAutoFocus = false; - - if (!didAutoFocus && autofocus) { - didAutoFocus = true; - SchedulerBinding.instance.addPostFrameCallback((_) { - FocusScope.of(context).autofocus(_focusNode); - }); - } - - return constrainedControl( - context, dropDown, widget.parent, widget.control); - }); + }).toList(); + + String? value = widget.control.getString("value"); + if (items.where((item) => item.value == value).isEmpty) { + value = null; + } + + TextCapitalization textCapitalization = widget.control + .getTextCapitalization("capitalization", TextCapitalization.none)!; + FilteringTextInputFormatter? inputFilter = + widget.control.getTextInputFormatter("input_filter"); + + List? inputFormatters = []; + if (inputFilter != null) { + inputFormatters.add(inputFilter); + } + if (textCapitalization != TextCapitalization.none) { + inputFormatters.add(TextCapitalizationFormatter(textCapitalization)); + } + + _focusNode.canRequestFocus = editable; + + Widget dropDown = DropdownMenu( + enabled: !widget.control.disabled, + focusNode: _focusNode, + initialSelection: value, + enableFilter: widget.control.getBool("enable_filter", false)!, + enableSearch: widget.control.getBool("enable_search", true)!, + menuHeight: widget.control.getDouble("menu_height"), + label: widget.control.buildTextOrWidget("label", + textStyle: widget.control.getTextStyle("label_style", theme)), + leadingIcon: widget.control.buildIconOrWidget("leading_icon"), + trailingIcon: widget.control.buildIconOrWidget("trailing_icon"), + selectedTrailingIcon: + widget.control.buildIconOrWidget("selected_trailing_icon"), + textStyle: textStyle, + textAlign: textAlign, + width: widget.control.getDouble("width"), + errorText: widget.control.getString("error_text"), + hintText: widget.control.getString("hint_text"), + helperText: widget.control.getString("helper_text"), + menuStyle: MenuStyle( + backgroundColor: widget.control.getWidgetStateColor("bgcolor", theme), + elevation: widget.control.getWidgetStateDouble("elevation"), + fixedSize: WidgetStateProperty.all(Size.fromWidth(menuWidth)), + ), + inputDecorationTheme: inputDecorationTheme, + onSelected: widget.control.disabled + ? null + : (String? value) { + widget.control.updateProperties({"value": value}); + widget.control.triggerEvent("change", value); + }, + dropdownMenuEntries: items, + ); + + var didAutoFocus = false; + + if (!didAutoFocus && autofocus) { + didAutoFocus = true; + SchedulerBinding.instance.addPostFrameCallback((_) { + FocusScope.of(context).autofocus(_focusNode); + }); + } + + return ConstrainedControl(control: widget.control, child: dropDown); } } diff --git a/packages/flet/lib/src/controls/dropdownm2.dart b/packages/flet/lib/src/controls/dropdownm2.dart index fcee09c18..b4e7b368c 100644 --- a/packages/flet/lib/src/controls/dropdownm2.dart +++ b/packages/flet/lib/src/controls/dropdownm2.dart @@ -1,267 +1,166 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import '../models/control_view_model.dart'; import '../utils/alignment.dart'; import '../utils/borders.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; import '../utils/form_field.dart'; -import '../utils/icons.dart'; +import '../utils/numbers.dart'; import '../utils/text.dart'; -import 'create_control.dart'; -import 'flet_store_mixin.dart'; +import 'base_controls.dart'; class DropdownM2Control extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const DropdownM2Control( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + DropdownM2Control({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _DropdownM2ControlState(); } -class _DropdownM2ControlState extends State - with FletStoreMixin { +class _DropdownM2ControlState extends State { String? _value; bool _focused = false; late final FocusNode _focusNode; - String? _lastFocusValue; @override void initState() { super.initState(); _focusNode = FocusNode(); _focusNode.addListener(_onFocusChange); + widget.control.addInvokeMethodListener(_invokeMethod); } void _onFocusChange() { setState(() { _focused = _focusNode.hasFocus; }); - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); + } + + Future _invokeMethod(String name, dynamic args) async { + debugPrint("DropdownM2.$name($args)"); + switch (name) { + case "focus": + _focusNode.requestFocus(); + default: + throw Exception("Unknown DropdownM2 method: $name"); + } } @override void dispose() { _focusNode.removeListener(_onFocusChange); _focusNode.dispose(); + widget.control.removeInvokeMethodListener(_invokeMethod); super.dispose(); } @override Widget build(BuildContext context) { - debugPrint("Dropdown build: ${widget.control.id}"); - - return withControls(widget.control.childIds, (context, itemsView) { - debugPrint("DropdownFletControlState build: ${widget.control.id}"); - - bool autofocus = widget.control.attrBool("autofocus", false)!; - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - var textSize = widget.control.attrDouble("textSize"); - var alignment = parseAlignment(widget.control, "alignment"); - var selectIconStr = parseIcon(widget.control.attrString("selectIcon")); - var selectIconCtrl = - widget.children.where((c) => c.name == "selectIcon" && c.isVisible); - var hintCtrl = - widget.children.where((c) => c.name == "hint" && c.isVisible); - var disabledHintCtrl = widget.children - .where((c) => c.name == "disabled_hint" && c.isVisible); - - var color = widget.control.attrColor("color", context); - var focusedColor = widget.control.attrColor("focusedColor", context); - var bgcolor = widget.control.attrColor("bgcolor", context); - var selectIconEnabledColor = - widget.control.attrColor("selectIconEnabledColor", context); - var selectIconDisabledColor = - widget.control.attrColor("selectIconDisabledColor", context); - - TextStyle? textStyle = - parseTextStyle(Theme.of(context), widget.control, "textStyle"); - if (textSize != null || color != null || focusedColor != null) { - textStyle = (textStyle ?? const TextStyle()).copyWith( - fontSize: textSize, - color: (_focused ? focusedColor ?? color : color) ?? - Theme.of(context).colorScheme.onSurface); - } - - var items = itemsView.controlViews - .where((c) => c.control.name == null && c.control.isVisible) - .map>((ControlViewModel itemCtrlView) { - var itemCtrl = itemCtrlView.control; - bool itemDisabled = disabled || itemCtrl.isDisabled; - TextStyle? textStyle = - parseTextStyle(Theme.of(context), itemCtrl, "textStyle"); - if (itemDisabled && textStyle != null) { - textStyle = textStyle.apply(color: Theme.of(context).disabledColor); - } - var contentCtrls = itemCtrlView.children - .where((c) => c.name == "content" && c.isVisible); - Widget? itemChild; - if (contentCtrls.isNotEmpty) { - // custom content - itemChild = createControl( - itemCtrlView.control, contentCtrls.first.id, itemDisabled); - } else { - itemChild = Text( - itemCtrl.attrs["text"] ?? itemCtrl.attrs["key"] ?? itemCtrl.id, - style: textStyle, - ); - } - var align = parseAlignment(itemCtrl, "alignment"); - if (align != null) { - itemChild = Container(alignment: align, child: itemChild); - } - return DropdownMenuItem( - enabled: !itemDisabled, - value: itemCtrl.attrs["key"] ?? itemCtrl.attrs["text"] ?? itemCtrl.id, - alignment: align ?? AlignmentDirectional.centerStart, - onTap: !(disabled || itemCtrl.isDisabled) - ? () { - widget.backend.triggerControlEvent(itemCtrl.id, "click"); - } - : null, - child: itemChild, - ); - }).toList(); - - String? value = widget.control.attrString("value"); - if (_value != value) { - _value = value; + debugPrint("DropdownM2 build: ${widget.control.id}"); + + var textSize = widget.control.getDouble("text_size"); + var color = widget.control.getColor("color", context); + var focusedColor = widget.control.getColor("focused_color", context); + + var textStyle = widget.control + .getTextStyle("text_style", Theme.of(context), const TextStyle())!; + if (textSize != null || color != null || focusedColor != null) { + textStyle = textStyle.copyWith( + fontSize: textSize, + color: (_focused ? (focusedColor ?? color) : color) ?? + Theme.of(context).colorScheme.onSurface); + } + + var items = widget.control + .children("options") + .map>((Control item) { + var textStyle = item.getTextStyle("text_style", Theme.of(context)); + if (item.disabled && textStyle != null) { + textStyle = textStyle.apply(color: Theme.of(context).disabledColor); } - - if (items.where((item) => item.value == value).isEmpty) { - _value = null; + var value = + item.getString("key") ?? item.getString("text") ?? item.id.toString(); + var content = + item.buildWidget("content") ?? Text(value, style: textStyle); + var alignment = item.getAlignment("alignment"); + if (alignment != null) { + content = Container(alignment: alignment, child: content); } - - var prefixControls = itemsView.controlViews - .where((c) => c.control.name == "prefix" && c.control.isVisible); - var prefixIconControls = itemsView.controlViews - .where((c) => c.control.name == "prefix_icon" && c.control.isVisible); - var suffixControls = itemsView.controlViews - .where((c) => c.control.name == "suffix" && c.control.isVisible); - var suffixIconControls = itemsView.controlViews - .where((c) => c.control.name == "suffix_icon" && c.control.isVisible); - var counterControls = itemsView.controlViews - .where((c) => c.control.name == "counter" && c.control.isVisible); - var iconControls = itemsView.controlViews - .where((c) => c.control.name == "icon" && c.control.isVisible); - var errorCtrl = itemsView.controlViews - .where((c) => c.control.name == "error" && c.control.isVisible); - var helperCtrl = itemsView.controlViews - .where((c) => c.control.name == "helper" && c.control.isVisible); - var labelCtrl = itemsView.controlViews - .where((c) => c.control.name == "label" && c.control.isVisible); - - var focusValue = widget.control.attrString("focus"); - if (focusValue != null && focusValue != _lastFocusValue) { - _lastFocusValue = focusValue; - _focusNode.requestFocus(); - } - - var borderRadius = parseBorderRadius(widget.control, "borderRadius"); - - Widget dropDown = DropdownButtonFormField( - style: textStyle, - autofocus: autofocus, - focusNode: _focusNode, - value: _value, - dropdownColor: bgcolor, - enableFeedback: widget.control.attrBool("enableFeedback"), - elevation: widget.control.attrInt("elevation", 8)!, - padding: parseEdgeInsets(widget.control, "padding"), - itemHeight: widget.control.attrDouble("itemHeight"), - menuMaxHeight: widget.control.attrDouble("maxMenuHeight"), - iconEnabledColor: selectIconEnabledColor, - iconDisabledColor: selectIconDisabledColor, - iconSize: widget.control.attrDouble("selectIconSize", 24.0)!, - borderRadius: borderRadius, - alignment: alignment ?? AlignmentDirectional.centerStart, - isExpanded: widget.control.attrBool("optionsFillHorizontally", true)!, - icon: selectIconCtrl.isNotEmpty - ? createControl(widget.control, selectIconCtrl.first.id, disabled) - : selectIconStr != null - ? Icon(selectIconStr) - : null, - hint: hintCtrl.isNotEmpty - ? createControl(widget.control, hintCtrl.first.id, disabled) - : null, - disabledHint: disabledHintCtrl.isNotEmpty - ? createControl(widget.control, disabledHintCtrl.first.id, disabled) - : null, - decoration: buildInputDecoration(context, widget.control, - prefix: - prefixControls.isNotEmpty ? prefixControls.first.control : null, - prefixIcon: prefixIconControls.isNotEmpty - ? prefixIconControls.first.control - : null, - suffix: - suffixControls.isNotEmpty ? suffixControls.first.control : null, - suffixIcon: suffixIconControls.isNotEmpty - ? suffixIconControls.first.control - : null, - counter: counterControls.isNotEmpty - ? counterControls.first.control - : null, - icon: iconControls.isNotEmpty ? iconControls.first.control : null, - error: errorCtrl.isNotEmpty ? errorCtrl.first.control : null, - helper: helperCtrl.isNotEmpty ? helperCtrl.first.control : null, - label: labelCtrl.isNotEmpty ? labelCtrl.first.control : null, - customSuffix: null, - focused: _focused, - disabled: disabled, - adaptive: widget.parentAdaptive), - onTap: !disabled - ? () { - widget.backend.triggerControlEvent(widget.control.id, "click"); - } - : null, - onChanged: disabled - ? null - : (String? value) { - debugPrint("Dropdown selected value: $value"); - _value = value!; - widget.backend - .updateControlState(widget.control.id, {"value": value}); - widget.backend - .triggerControlEvent(widget.control.id, "change", value); - }, - items: items, - ); - - if (widget.control.attrInt("expand", 0)! > 0) { - return constrainedControl( - context, dropDown, widget.parent, widget.control); - } else { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - if (constraints.maxWidth == double.infinity && - widget.control.attrDouble("width") == null) { - dropDown = ConstrainedBox( + return DropdownMenuItem( + enabled: !item.disabled, + value: value, + alignment: alignment ?? AlignmentDirectional.centerStart, + onTap: !item.disabled ? () => item.triggerEvent("click") : null, + child: content); + }).toList(); + + String? value = widget.control.getString("value"); + if (_value != value) { + _value = value; + } + + if (items.where((item) => item.value == value).isEmpty) { + _value = null; + } + + Widget dropDown = DropdownButtonFormField( + style: textStyle, + autofocus: widget.control.getBool("autofocus", false)!, + focusNode: _focusNode, + value: _value, + dropdownColor: widget.control.getColor("bgcolor", context), + enableFeedback: widget.control.getBool("enable_feedback"), + elevation: widget.control.getInt("elevation", 8)!, + padding: widget.control.getPadding("padding"), + itemHeight: widget.control.getDouble("item_height"), + menuMaxHeight: widget.control.getDouble("max_menu_height"), + iconEnabledColor: + widget.control.getColor("select_icon_enabled_color", context), + iconDisabledColor: + widget.control.getColor("select_icon_disabled_color", context), + iconSize: widget.control.getDouble("select_icon_size", 24.0)!, + borderRadius: widget.control.getBorderRadius("border_radius"), + alignment: widget.control.getAlignment("alignment") ?? + AlignmentDirectional.centerStart, + isExpanded: widget.control.getBool("options_fill_horizontally", true)!, + icon: widget.control.buildIconOrWidget("select_icon"), + hint: widget.control.buildWidget("hint"), + disabledHint: widget.control.buildWidget("disabled_hint"), + decoration: buildInputDecoration(context, widget.control, + customSuffix: null, focused: _focused), + onTap: !widget.control.disabled + ? () => widget.control.triggerEvent("click") + : null, + onChanged: widget.control.disabled + ? null + : (String? value) { + _value = value!; + widget.control.updateProperties({"value": value}); + widget.control.triggerEvent("change", value); + }, + items: items, + ); + + if (widget.control.getInt("expand", 0)! > 0) { + return ConstrainedControl(control: widget.control, child: dropDown); + } else { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + if (constraints.maxWidth == double.infinity && + widget.control.getDouble("width") == null) { + dropDown = ConstrainedBox( constraints: const BoxConstraints.tightFor(width: 300), - child: dropDown, - ); - } + child: dropDown); + } - return constrainedControl( - context, dropDown, widget.parent, widget.control); - }, - ); - } - }); + return ConstrainedControl(control: widget.control, child: dropDown); + }, + ); + } } } diff --git a/packages/flet/lib/src/controls/elevated_button.dart b/packages/flet/lib/src/controls/elevated_button.dart deleted file mode 100644 index a6adeb63e..000000000 --- a/packages/flet/lib/src/controls/elevated_button.dart +++ /dev/null @@ -1,245 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../flet_control_backend.dart'; -import '../models/control.dart'; -import '../utils/buttons.dart'; -import '../utils/icons.dart'; -import '../utils/launch_url.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'cupertino_button.dart'; -import 'cupertino_dialog_action.dart'; -import 'error.dart'; -import 'flet_store_mixin.dart'; - -class ElevatedButtonControl extends StatefulWidget { - final Control? parent; - final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - - const ElevatedButtonControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); - - @override - State createState() => _ElevatedButtonControlState(); -} - -class _ElevatedButtonControlState extends State - with FletStoreMixin { - late final FocusNode _focusNode; - String? _lastFocusValue; - - @override - void initState() { - super.initState(); - _focusNode = FocusNode(); - _focusNode.addListener(_onFocusChange); - } - - @override - void dispose() { - _focusNode.removeListener(_onFocusChange); - _focusNode.dispose(); - super.dispose(); - } - - void _onFocusChange() { - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); - } - - @override - Widget build(BuildContext context) { - debugPrint("Button build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - return withPagePlatform((context, platform) { - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - if (adaptive == true && - (platform == TargetPlatform.iOS || - platform == TargetPlatform.macOS)) { - return widget.control.name == "action" && - (widget.parent?.type == "alertdialog" || - widget.parent?.type == "cupertinoalertdialog") - ? CupertinoDialogActionControl( - control: widget.control, - parentDisabled: widget.parentDisabled, - parentAdaptive: adaptive, - children: widget.children, - backend: widget.backend) - : CupertinoButtonControl( - control: widget.control, - parentDisabled: widget.parentDisabled, - parentAdaptive: adaptive, - children: widget.children, - backend: widget.backend); - } - - bool isFilledButton = widget.control.type == "filledbutton"; - bool isFilledTonalButton = widget.control.type == "filledtonalbutton"; - String text = widget.control.attrString("text", "")!; - String url = widget.control.attrString("url", "")!; - IconData? icon = parseIcon(widget.control.attrString("icon")); - Color? iconColor = widget.control.attrColor("iconColor", context); - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); - var clipBehavior = - parseClip(widget.control.attrString("clipBehavior"), Clip.none)!; - bool onHover = widget.control.attrBool("onHover", false)!; - bool onLongPress = widget.control.attrBool("onLongPress", false)!; - bool autofocus = widget.control.attrBool("autofocus", false)!; - - Function()? onPressed = !disabled - ? () { - debugPrint("Button ${widget.control.id} clicked!"); - if (url != "") { - openWebBrowser(url, - webWindowName: widget.control.attrString("urlTarget")); - } - widget.backend.triggerControlEvent(widget.control.id, "click"); - } - : null; - - Function()? onLongPressHandler = onLongPress && !disabled - ? () { - debugPrint("Button ${widget.control.id} long pressed!"); - widget.backend - .triggerControlEvent(widget.control.id, "long_press"); - } - : null; - - Function(bool)? onHoverHandler = onHover && !disabled - ? (state) { - debugPrint("Button ${widget.control.id} hovered!"); - widget.backend.triggerControlEvent( - widget.control.id, "hover", state.toString()); - } - : null; - - Widget? button; - - var theme = Theme.of(context); - - var style = parseButtonStyle(Theme.of(context), widget.control, "style", - defaultForegroundColor: theme.colorScheme.primary, - defaultBackgroundColor: theme.colorScheme.surface, - defaultOverlayColor: theme.colorScheme.primary.withOpacity(0.08), - defaultShadowColor: theme.colorScheme.shadow, - defaultSurfaceTintColor: theme.colorScheme.surfaceTint, - defaultElevation: 1, - defaultPadding: const EdgeInsets.symmetric(horizontal: 8), - defaultBorderSide: BorderSide.none, - defaultShape: theme.useMaterial3 - ? const StadiumBorder() - : RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))); - - if (icon != null) { - if (text == "") { - return const ErrorControl("Error displaying ElevatedButton", - description: - "\"icon\" must be specified together with \"text\"."); - } - if (isFilledButton) { - button = FilledButton.icon( - style: style, - autofocus: autofocus, - focusNode: _focusNode, - onPressed: onPressed, - onLongPress: onLongPressHandler, - onHover: onHoverHandler, - clipBehavior: clipBehavior, - icon: Icon( - icon, - color: iconColor, - ), - label: Text(text)); - } else if (isFilledTonalButton) { - button = FilledButton.tonalIcon( - style: style, - autofocus: autofocus, - focusNode: _focusNode, - onPressed: onPressed, - onLongPress: onLongPressHandler, - onHover: onHoverHandler, - clipBehavior: clipBehavior, - icon: Icon( - icon, - color: iconColor, - ), - label: Text(text)); - } else { - button = ElevatedButton.icon( - style: style, - autofocus: autofocus, - focusNode: _focusNode, - onPressed: onPressed, - onLongPress: onLongPressHandler, - onHover: onHoverHandler, - clipBehavior: clipBehavior, - icon: Icon( - icon, - color: iconColor, - ), - label: Text(text)); - } - } else { - Widget? child; - if (contentCtrls.isNotEmpty) { - child = createControl(widget.control, contentCtrls.first.id, disabled, - parentAdaptive: adaptive); - } else { - child = Text(text); - } - - if (isFilledButton) { - button = FilledButton( - style: style, - autofocus: autofocus, - focusNode: _focusNode, - onPressed: onPressed, - onLongPress: onLongPressHandler, - onHover: onHoverHandler, - clipBehavior: clipBehavior, - child: child); - } else if (isFilledTonalButton) { - button = FilledButton.tonal( - style: style, - autofocus: autofocus, - focusNode: _focusNode, - onPressed: onPressed, - onLongPress: onLongPressHandler, - onHover: onHoverHandler, - clipBehavior: clipBehavior, - child: child); - } else { - button = ElevatedButton( - style: style, - autofocus: autofocus, - focusNode: _focusNode, - onPressed: onPressed, - onLongPress: onLongPressHandler, - onHover: onHoverHandler, - clipBehavior: clipBehavior, - child: child); - } - } - - var focusValue = widget.control.attrString("focus"); - if (focusValue != null && focusValue != _lastFocusValue) { - _lastFocusValue = focusValue; - _focusNode.requestFocus(); - } - return constrainedControl(context, button, widget.parent, widget.control); - }); - } -} diff --git a/packages/flet/lib/src/controls/expansion_panel.dart b/packages/flet/lib/src/controls/expansion_panel.dart index 6b6623d2e..35d53b65d 100644 --- a/packages/flet/lib/src/controls/expansion_panel.dart +++ b/packages/flet/lib/src/controls/expansion_panel.dart @@ -1,99 +1,60 @@ +import 'package:flet/src/extensions/control.dart'; +import 'package:flet/src/utils/colors.dart'; +import 'package:flet/src/utils/edge_insets.dart'; +import 'package:flet/src/utils/numbers.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; -import '../utils/edge_insets.dart'; -import 'create_control.dart'; -import 'flet_store_mixin.dart'; +import 'base_controls.dart'; -class ExpansionPanelListControl extends StatefulWidget { - final Control? parent; +class ExpansionPanelListControl extends StatelessWidget { final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const ExpansionPanelListControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const ExpansionPanelListControl({ + super.key, + required this.control, + }); @override - State createState() => - _ExpansionPanelListControlState(); -} - -class _ExpansionPanelListControlState extends State - with FletStoreMixin { - @override Widget build(BuildContext context) { - debugPrint("ExpansionPanelList build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - - var panels = widget.children - .where((c) => c.name == "expansionpanel" && c.isVisible) - .toList(); + debugPrint("ExpansionPanelList build: ${control.id}"); void onChange(int index, bool isExpanded) { - widget.backend.updateControlState( - panels[index].id, {"expanded": isExpanded.toString().toLowerCase()}); - widget.backend.triggerControlEvent(widget.control.id, "change", "$index"); + control + .children("controls")[index] + .updateProperties({"expanded": isExpanded}, notify: true); + control.triggerEvent("change", index); } - var panelList = - withControls(panels.map((p) => p.id), (content, panelViews) { - return ExpansionPanelList( - elevation: widget.control.attrDouble("elevation", 2)!, - materialGapSize: widget.control.attrDouble("spacing", 16)!, - dividerColor: widget.control.attrColor("dividerColor", context), - expandIconColor: - widget.control.attrColor("expandedIconColor", context), - expandedHeaderPadding: parseEdgeInsets( - widget.control, - "expandedHeaderPadding", - const EdgeInsets.symmetric(vertical: 16))!, - expansionCallback: !disabled - ? (int index, bool isExpanded) { - onChange(index, isExpanded); - } - : null, - children: panelViews.controlViews.map((panelView) { - var headerCtrls = panelView.children - .where((c) => c.name == "header" && c.isVisible); - var bodyCtrls = panelView.children - .where((c) => c.name == "content" && c.isVisible); - - return ExpansionPanel( - backgroundColor: panelView.control.attrColor("bgColor", context), - isExpanded: panelView.control.attrBool("expanded", false)!, - highlightColor: - panelView.control.attrColor("highlightColor", context), - splashColor: panelView.control.attrColor("splashColor", context), - canTapOnHeader: - panelView.control.attrBool("canTapHeader", false)!, - headerBuilder: (BuildContext context, bool isExpanded) { - return headerCtrls.isNotEmpty - ? createControl( - widget.control, headerCtrls.first.id, disabled, - parentAdaptive: adaptive) - : const ListTile(title: Text("Header Placeholder")); - }, - body: bodyCtrls.isNotEmpty - ? createControl(widget.control, bodyCtrls.first.id, disabled, - parentAdaptive: adaptive) - : const ListTile(title: Text("Body Placeholder")), - ); - }).toList()); - }); - - return constrainedControl( - context, panelList, widget.parent, widget.control); + var panelList = ExpansionPanelList( + elevation: control.getDouble("elevation", 2)!, + materialGapSize: control.getDouble("spacing", 16)!, + dividerColor: control.getColor("divider_color", context), + expandIconColor: control.getColor("expanded_icon_color", context), + expandedHeaderPadding: control.getEdgeInsets("expanded_header_padding", + const EdgeInsets.symmetric(vertical: 16))!, + expansionCallback: !control.disabled + ? (int index, bool isExpanded) { + onChange(index, isExpanded); + } + : null, + children: control.children("controls").map((panelControl) { + panelControl.notifyParent = true; + return ExpansionPanel( + backgroundColor: panelControl.getColor("bgcolor", context), + isExpanded: panelControl.getBool("expanded", false)!, + highlightColor: panelControl.getColor("highlight_color", context), + splashColor: panelControl.getColor("splash_color", context), + canTapOnHeader: panelControl.getBool("can_tap_header", false)!, + headerBuilder: (BuildContext context, bool isExpanded) { + return panelControl.buildWidget("header") ?? + const ListTile(title: Text("Header Placeholder")); + }, + body: panelControl.buildWidget("content") ?? + const ListTile(title: Text("Body Placeholder")), + ); + }).toList()); + + return ConstrainedControl(control: control, child: panelList); } } diff --git a/packages/flet/lib/src/controls/expansion_tile.dart b/packages/flet/lib/src/controls/expansion_tile.dart index 1a0c43cd9..c31fc4f8a 100644 --- a/packages/flet/lib/src/controls/expansion_tile.dart +++ b/packages/flet/lib/src/controls/expansion_tile.dart @@ -1,70 +1,62 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/alignment.dart'; import '../utils/borders.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; -import '../utils/others.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; import '../utils/theme.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; +import 'control_widget.dart'; class ExpansionTileControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const ExpansionTileControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const ExpansionTileControl({ + super.key, + required this.control, + }); @override Widget build(BuildContext context) { debugPrint("ExpansionTile build: ${control.id}"); - var ctrls = children.where((c) => c.name == "controls" && c.isVisible); - var leadingCtrls = - children.where((c) => c.name == "leading" && c.isVisible); - var titleCtrls = children.where((c) => c.name == "title" && c.isVisible); - var subtitleCtrls = - children.where((c) => c.name == "subtitle" && c.isVisible); - var trailingCtrls = - children.where((c) => c.name == "trailing" && c.isVisible); + var controls = control + .children("controls") + .map((child) => ControlWidget(control: child, key: ValueKey(child.id))) + .toList(); - if (titleCtrls.isEmpty) { + var leading = control.buildIconOrWidget("leading"); + var title = control.buildTextOrWidget("title"); + var subtitle = control.buildTextOrWidget("subtitle"); + var trailing = control.buildIconOrWidget("trailing"); + + if (title == null) { return const ErrorControl( "ExpansionTile.title must be provided and visible"); } - bool disabled = control.isDisabled || parentDisabled; - bool? adaptive = control.attrBool("adaptive") ?? parentAdaptive; - bool onchange = control.attrBool("onchange", false)!; - bool maintainState = control.attrBool("maintainState", false)!; - bool initiallyExpanded = control.attrBool("initiallyExpanded", false)!; + bool maintainState = control.getBool("maintain_state", false)!; + bool initiallyExpanded = control.getBool("initially_expanded", false)!; - var iconColor = control.attrColor("iconColor", context); - var textColor = control.attrColor("textColor", context); - var bgColor = control.attrColor("bgColor", context); - var collapsedBgColor = control.attrColor("collapsedBgColor", context); - var collapsedIconColor = control.attrColor("collapsedIconColor", context); - var collapsedTextColor = control.attrColor("collapsedTextColor", context); + var iconColor = control.getColor("icon_color", context); + var textColor = control.getColor("text_color", context); + var bgColor = control.getColor("bgcolor", context); + var collapsedBgColor = control.getColor("collapsed_bgcolor", context); + var collapsedIconColor = control.getColor("collapsed_icon_color", context); + var collapsedTextColor = control.getColor("collapsed_text_color", context); - var affinity = parseListTileControlAffinity( - control.attrString("affinity"), ListTileControlAffinity.platform)!; + var affinity = control.getListTileControlAffinity( + "affinity", ListTileControlAffinity.platform)!; var clipBehavior = - parseClip(control.attrString("clipBehavior"), Clip.none)!; + parseClip(control.getString("clip_behavior"), Clip.none)!; - var expandedCrossAxisAlignment = parseCrossAxisAlignment( - control.attrString("crossAxisAlignment"), CrossAxisAlignment.center)!; + var expandedCrossAxisAlignment = control.getCrossAxisAlignment( + "expanded_cross_axis_alignment", CrossAxisAlignment.center)!; if (expandedCrossAxisAlignment == CrossAxisAlignment.baseline) { return const ErrorControl( @@ -73,21 +65,19 @@ class ExpansionTileControl extends StatelessWidget { 'Try aligning the controls differently.'); } - Function(bool)? onChange = (onchange) && !disabled + Function(bool)? onChange = !control.disabled ? (expanded) { - debugPrint( - "ExpansionTile ${control.id} was ${expanded ? "expanded" : "collapsed"}"); - backend.triggerControlEvent(control.id, "change", "$expanded"); + control.triggerEvent("change", expanded); } : null; Widget tile = ExpansionTile( controlAffinity: affinity, - childrenPadding: parseEdgeInsets(control, "controlsPadding"), - tilePadding: parseEdgeInsets(control, "tilePadding"), - expandedAlignment: parseAlignment(control, "expandedAlignment"), + childrenPadding: control.getPadding("controls_padding"), + tilePadding: control.getEdgeInsets("tile_padding"), + expandedAlignment: control.getAlignment("expanded_alignment"), expandedCrossAxisAlignment: - parseCrossAxisAlignment(control.attrString("crossAxisAlignment")), + control.getCrossAxisAlignment("expanded_cross_axis_alignment"), backgroundColor: bgColor, iconColor: iconColor, textColor: textColor, @@ -97,35 +87,22 @@ class ExpansionTileControl extends StatelessWidget { maintainState: maintainState, initiallyExpanded: initiallyExpanded, clipBehavior: clipBehavior, - shape: parseOutlinedBorder(control, "shape"), - collapsedShape: parseOutlinedBorder(control, "collapsedShape"), + shape: control.getShape("shape", Theme.of(context)), + collapsedShape: control.getShape("collapsed_shape", Theme.of(context)), onExpansionChanged: onChange, - visualDensity: parseVisualDensity(control.attrString("visualDensity")), - enableFeedback: control.attrBool("enableFeedback"), - showTrailingIcon: control.attrBool("showTrailingIcon", true)!, - enabled: !disabled, - minTileHeight: control.attrDouble("minTileHeight"), - dense: control.attrBool("dense"), - leading: leadingCtrls.isNotEmpty - ? createControl(control, leadingCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null, - title: createControl(control, titleCtrls.first.id, disabled, - parentAdaptive: adaptive), - subtitle: subtitleCtrls.isNotEmpty - ? createControl(control, subtitleCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null, - trailing: trailingCtrls.isNotEmpty - ? createControl(control, trailingCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null, - children: ctrls - .map((c) => - createControl(control, c.id, disabled, parentAdaptive: adaptive)) - .toList(), + visualDensity: control.getVisualDensity("visual_density"), + enableFeedback: control.getBool("enable_feedback"), + showTrailingIcon: control.getBool("show_trailing_icon", true)!, + enabled: !control.disabled, + minTileHeight: control.getDouble("min_tile_height"), + dense: control.getBool("dense"), + leading: leading, + title: title, + subtitle: subtitle, + trailing: trailing, + children: controls, ); - return constrainedControl(context, tile, parent, control); + return ConstrainedControl(control: control, child: tile); } } diff --git a/packages/flet/lib/src/controls/file_picker.dart b/packages/flet/lib/src/controls/file_picker.dart deleted file mode 100644 index 320f17e72..000000000 --- a/packages/flet/lib/src/controls/file_picker.dart +++ /dev/null @@ -1,337 +0,0 @@ -import 'dart:convert'; - -import 'package:collection/collection.dart'; -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; - -import '../flet_app_services.dart'; -import '../flet_control_backend.dart'; -import '../flet_server.dart'; -import '../models/control.dart'; -import '../utils/numbers.dart'; -import '../utils/platform.dart'; -import '../utils/strings.dart'; -import 'flet_store_mixin.dart'; - -class FilePickerResultEvent { - final String? path; - final List? files; - final String? error; - - FilePickerResultEvent( - {required this.path, required this.files, required this.error}); - - Map toJson() => { - 'path': path, - 'files': files?.map((f) => f.toJson()).toList(), - 'error': error - }; -} - -class FilePickerFile { - final int id; - final String name; - final String? path; - final int size; - - FilePickerFile( - {required this.id, - required this.name, - required this.path, - required this.size}); - - Map toJson() => - {'id': id, 'name': name, 'path': path, 'size': size}; -} - -class FilePickerUploadFile { - final int id; - final String name; - final String uploadUrl; - final String method; - - FilePickerUploadFile( - {required this.id, - required this.name, - required this.uploadUrl, - required this.method}); -} - -class FilePickerUploadProgressEvent { - final String name; - final double? progress; - final String? error; - - FilePickerUploadProgressEvent( - {required this.name, required this.progress, required this.error}); - - Map toJson() => { - 'file_name': name, - 'progress': progress, - 'error': error - }; -} - -class FilePickerControl extends StatefulWidget { - final Control? parent; - final Control control; - final Widget? nextChild; - final FletControlBackend backend; - - const FilePickerControl( - {super.key, - required this.parent, - required this.control, - required this.nextChild, - required this.backend}); - - @override - State createState() => _FilePickerControlState(); -} - -class _FilePickerControlState extends State - with FletStoreMixin { - String? _state; - String? _upload; - String? _path; - List? _files; - String? _error; - - @override - Widget build(BuildContext context) { - debugPrint("FilePicker build: ${widget.control.id}"); - - return withPageArgs((context, pageArgs) { - var state = widget.control.attrString("state"); - var upload = widget.control.attrString("upload"); - var dialogTitle = widget.control.attrString("dialogTitle"); - var fileName = widget.control.attrString("fileName"); - var initialDirectory = widget.control.attrString("initialDirectory"); - var allowMultiple = widget.control.attrBool("allowMultiple", false)!; - var allowedExtensions = - parseStringList(widget.control, "allowedExtensions"); - var srcBytesBase64 = widget.control.attrString("srcBytes"); - FileType fileType = FileType.values.firstWhere( - (m) => - m.name.toLowerCase() == - widget.control.attrString("fileType", "")!.toLowerCase(), - orElse: () => FileType.any); - if (allowedExtensions != null && allowedExtensions.isNotEmpty) { - fileType = FileType.custom; - } - - debugPrint("FilePicker _state: $_state, state: $state"); - - resetDialogState() { - _state = null; - widget.backend.updateControlState(widget.control.id, {"state": ""}); - } - - sendEvent() { - if (defaultTargetPlatform != TargetPlatform.windows || - !isDesktopPlatform()) { - resetDialogState(); - } - - widget.backend.triggerControlEvent( - widget.control.id, - "result", - json.encode(FilePickerResultEvent( - path: _path, - files: _files?.asMap().entries.map((entry) { - PlatformFile f = entry.value; - return FilePickerFile( - id: entry.key, // use entry's index as id - name: f.name, - path: kIsWeb ? null : f.path, - size: f.size, - ); - }).toList(), - error: _error)), - ); - } - - if (_state != state) { - _path = null; - _files = null; - _error = null; - _state = state; - - if (isDesktopPlatform() && - defaultTargetPlatform == TargetPlatform.windows) { - resetDialogState(); - } - - // pickFiles - if (state?.toLowerCase() == "pickfiles") { - FilePicker.platform - .pickFiles( - dialogTitle: dialogTitle, - initialDirectory: initialDirectory, - lockParentWindow: true, - type: fileType, - allowedExtensions: allowedExtensions, - allowMultiple: allowMultiple, - withData: false, - withReadStream: true) - .then((FilePickerResult? result) { - debugPrint("pickFiles() completed"); - _files = result?.files; - sendEvent(); - }); - } - // saveFile - else if (state?.toLowerCase() == "savefile" && !kIsWeb) { - if ((isAndroidPlatform() || isiOSPlatform()) && - srcBytesBase64 == null) { - _error = - '"src_bytes" is required on Android & iOS when saving a file.'; - sendEvent(); - } else { - FilePicker.platform - .saveFile( - dialogTitle: dialogTitle, - fileName: fileName != null || !isiOSPlatform() - ? fileName - : "new-file", - initialDirectory: initialDirectory, - lockParentWindow: true, - type: fileType, - allowedExtensions: allowedExtensions, - bytes: srcBytesBase64 != null - ? base64Decode(srcBytesBase64) - : null) - .then((result) { - debugPrint("saveFile() completed"); - _path = result; - sendEvent(); - }); - } - } - // getDirectoryPath - else if (state?.toLowerCase() == "getdirectorypath" && !kIsWeb) { - FilePicker.platform - .getDirectoryPath( - dialogTitle: dialogTitle, - initialDirectory: initialDirectory, - lockParentWindow: true, - ) - .then((result) { - debugPrint("getDirectoryPath() completed"); - _path = result; - sendEvent(); - }); - } - } - - // upload files - if (_upload != upload && upload != null && _files != null) { - _upload = upload; - uploadFiles( - upload, FletAppServices.of(context).server, pageArgs.pageUri!); - } - - return widget.nextChild ?? const SizedBox.shrink(); - }); - } - - Future uploadFiles(String filesJson, FletServer server, Uri pageUri) async { - var uj = json.decode(filesJson); - var uploadFiles = (uj as List).map((u) => FilePickerUploadFile( - id: parseInt(u["id"], -1)!, // -1 = invalid - name: u["name"], - uploadUrl: u["upload_url"], - method: u["method"])); - - for (var uf in uploadFiles) { - var file = ((uf.id >= 0 && uf.id < _files!.length) - ? _files![uf.id] - : null) // by id - ?? - _files!.firstWhereOrNull((f) => f.name == uf.name); // by name - - if (file != null) { - try { - await uploadFile( - file, server, getFullUploadUrl(pageUri, uf.uploadUrl), uf.method); - _files!.remove(file); // Remove the uploaded file - } catch (e) { - sendProgress(server, file.name, null, e.toString()); - } - } else { - debugPrint("Error: File '${uf.name}' (id: ${uf.id}) not found."); - } - } - } - - Future uploadFile(PlatformFile file, FletServer server, String uploadUrl, - String method) async { - final fileReadStream = file.readStream; - if (fileReadStream == null) { - throw Exception('Cannot read file from null stream'); - } - debugPrint("Uploading ${file.name}"); - final streamedRequest = http.StreamedRequest(method, Uri.parse(uploadUrl)) - ..headers.addAll({ - //'Cache-Control': 'no-cache', - }); - streamedRequest.contentLength = file.size; - - // send 0% - sendProgress(server, file.name, 0, null); - - double lastSent = 0; // send every 10% - double progress = 0; - int bytesSent = 0; - fileReadStream.listen((chunk) async { - //debugPrint(chunk.length); - streamedRequest.sink.add(chunk); - bytesSent += chunk.length; - progress = bytesSent / file.size; - if (progress >= lastSent) { - lastSent += 0.1; - if (progress != 1.0) { - sendProgress(server, file.name, progress, null); - } - } - }, onDone: () { - streamedRequest.sink.close(); - }); - - var streamedResponse = await streamedRequest.send(); - var response = await http.Response.fromStream(streamedResponse); - if (response.statusCode < 200 || response.statusCode > 204) { - sendProgress(server, file.name, null, - "Upload endpoint returned code ${response.statusCode}: ${response.body}"); - } else { - // send 100% - sendProgress(server, file.name, progress, null); - } - } - - void sendProgress( - FletServer server, String name, double? progress, String? error) { - widget.backend.triggerControlEvent( - widget.control.id, - "upload", - json.encode(FilePickerUploadProgressEvent( - name: name, progress: progress, error: error))); - } - - String getFullUploadUrl(Uri pageUri, String uploadUrl) { - Uri uploadUri = Uri.parse(uploadUrl); - if (!uploadUri.hasAuthority) { - return Uri( - scheme: pageUri.scheme, - host: pageUri.host, - port: pageUri.port, - path: uploadUri.path, - query: uploadUri.query) - .toString(); - } else { - return uploadUrl; - } - } -} diff --git a/packages/flet/lib/src/controls/flet_app_control.dart b/packages/flet/lib/src/controls/flet_app_control.dart index bdb5d7d07..7de97c312 100644 --- a/packages/flet/lib/src/controls/flet_app_control.dart +++ b/packages/flet/lib/src/controls/flet_app_control.dart @@ -2,16 +2,16 @@ import 'package:flutter/material.dart'; import '../flet_app.dart'; import '../flet_app_errors_handler.dart'; -import '../flet_app_services.dart'; +import '../flet_backend.dart'; import '../models/control.dart'; -import 'create_control.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; class FletAppControl extends StatefulWidget { - final Control? parent; final Control control; - const FletAppControl( - {super.key, required this.parent, required this.control}); + FletAppControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _FletAppControlState(); @@ -24,28 +24,31 @@ class _FletAppControlState extends State { Widget build(BuildContext context) { debugPrint("FletApp build: ${widget.control.id}"); - var url = widget.control.attrString("url", "")!; - var reconnectIntervalMs = widget.control.attrInt("reconnectIntervalMs"); - var reconnectTimeoutMs = widget.control.attrInt("reconnectTimeoutMs"); - var showAppStartupScreen = widget.control.attrBool("showAppStartupScreen"); + var url = widget.control.getString("url", "")!; + var reconnectIntervalMs = widget.control.getInt("reconnect_interval_ms"); + var reconnectTimeoutMs = widget.control.getInt("reconnect_timeout_ms"); + var showAppStartupScreen = + widget.control.getBool("show_app_startup_screen"); var appStartupScreenMessage = - widget.control.attrString("appStartupScreenMessage"); - - return constrainedControl( - context, - FletApp( - controlId: widget.control.id, - reconnectIntervalMs: reconnectIntervalMs, - reconnectTimeoutMs: reconnectTimeoutMs, - showAppStartupScreen: showAppStartupScreen, - appStartupScreenMessage: appStartupScreenMessage, - pageUrl: url, - assetsDir: "", - errorsHandler: _errorsHandler, - createControlFactories: - FletAppServices.of(context).createControlFactories, - ), - widget.parent, - widget.control); + widget.control.getString("app_startup_screen_message"); + + return ConstrainedControl( + control: widget.control, + child: FletApp( + controlId: widget.control.id, + reconnectIntervalMs: reconnectIntervalMs, + reconnectTimeoutMs: reconnectTimeoutMs, + showAppStartupScreen: showAppStartupScreen, + appStartupScreenMessage: appStartupScreenMessage, + pageUrl: url, + assetsDir: "", + errorsHandler: _errorsHandler, + extensions: FletBackend.of(context).extensions, + args: widget.control.get("args") != null + ? Map.from(widget.control.get("args")) + : null, + forcePyodide: widget.control.getBool("force_pyodide"), + ), + ); } } diff --git a/packages/flet/lib/src/controls/flet_store_mixin.dart b/packages/flet/lib/src/controls/flet_store_mixin.dart deleted file mode 100644 index 24a69de2d..000000000 --- a/packages/flet/lib/src/controls/flet_store_mixin.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_redux/flutter_redux.dart'; - -import '../models/app_state.dart'; -import '../models/control.dart'; -import '../models/control_ancestor_view_model.dart'; -import '../models/control_tree_view_model.dart'; -import '../models/control_view_model.dart'; -import '../models/controls_view_model.dart'; -import '../models/page_args_model.dart'; -import '../models/page_size_view_model.dart'; - -mixin FletStoreMixin { - Widget withPageArgs(Widget Function(BuildContext, PageArgsModel) build) { - return StoreConnector( - distinct: true, - converter: (store) => PageArgsModel.fromStore(store), - builder: build); - } - - Widget withPageSize(Widget Function(BuildContext, PageSizeViewModel) build) { - return StoreConnector( - distinct: true, - converter: (store) => PageSizeViewModel.fromStore(store), - builder: build); - } - - Widget withPagePlatform(Widget Function(BuildContext, TargetPlatform) build) { - return StoreConnector( - distinct: true, - converter: (store) => TargetPlatform.values.firstWhere( - (a) => - a.name.toLowerCase() == - store.state.controls["page"]! - .attrString("platform", "")! - .toLowerCase(), - orElse: () => defaultTargetPlatform), - builder: build); - } - - Widget withControl( - String id, Widget Function(BuildContext, ControlViewModel?) build) { - return StoreConnector( - distinct: true, - converter: (store) { - return ControlViewModel.fromStore(store, id); - }, - ignoreChange: (state) { - return state.controls[id] == null; - }, - builder: build); - } - - Widget withControlTree(Control control, - Widget Function(BuildContext, ControlTreeViewModel) build) { - return StoreConnector( - distinct: true, - converter: (store) => ControlTreeViewModel.fromStore(store, control), - builder: build); - } - - Widget withControlAncestor(String id, String ancestorType, - Widget Function(BuildContext, ControlAncestorViewModel) build) { - return StoreConnector( - distinct: true, - converter: (store) => - ControlAncestorViewModel.fromStore(store, id, ancestorType), - ignoreChange: (state) { - return state.controls[id] == null; - }, - builder: build); - } - - Widget withControls(Iterable controlIds, - Widget Function(BuildContext, ControlsViewModel) build) { - return StoreConnector( - distinct: true, - converter: (store) => ControlsViewModel.fromStore(store, controlIds), - ignoreChange: (state) { - for (var id in controlIds) { - if (state.controls[id] == null) { - return true; - } - } - return false; - }, - builder: build); - } -} diff --git a/packages/flet/lib/src/controls/floating_action_button.dart b/packages/flet/lib/src/controls/floating_action_button.dart index 967c71462..064aab2bb 100644 --- a/packages/flet/lib/src/controls/floating_action_button.dart +++ b/packages/flet/lib/src/controls/floating_action_button.dart @@ -1,84 +1,77 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/borders.dart'; -import '../utils/icons.dart'; +import '../utils/colors.dart'; import '../utils/launch_url.dart'; +import '../utils/misc.dart'; import '../utils/mouse.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; class FloatingActionButtonControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const FloatingActionButtonControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const FloatingActionButtonControl({ + super.key, + required this.control, + }); @override Widget build(BuildContext context) { debugPrint("FloatingActionButtonControl build: ${control.id}"); - String? text = control.attrString("text"); - IconData? icon = parseIcon(control.attrString("icon")); - String url = control.attrString("url", "")!; - String? urlTarget = control.attrString("urlTarget"); - double? disabledElevation = control.attrDouble("disabledElevation"); - double? elevation = control.attrDouble("elevation"); - double? hoverElevation = control.attrDouble("hoverElevation"); - double? highlightElevation = control.attrDouble("highlightElevation"); - double? focusElevation = control.attrDouble("focusElevation"); - Color? bgColor = control.attrColor("bgColor", context); - Color? foregroundColor = control.attrColor("foregroundColor", context); - Color? splashColor = control.attrColor("splashColor", context); - Color? hoverColor = control.attrColor("hoverColor", context); - Color? focusColor = control.attrColor("focusColor", context); - OutlinedBorder? shape = parseOutlinedBorder(control, "shape"); + var content = control.buildTextOrWidget("content"); + var icon = control.buildIconOrWidget("icon"); + var url = control.getString("url"); + var urlTarget = control.getString("url_target"); + var disabledElevation = control.getDouble("disabled_elevation"); + var elevation = control.getDouble("elevation"); + var hoverElevation = control.getDouble("hover_elevation"); + var highlightElevation = control.getDouble("highlight_elevation"); + var focusElevation = control.getDouble("focus_elevation"); + var bgcolor = control.getColor("bgcolor", context); + var foregroundColor = control.getColor("foreground_color", context); + var splashColor = control.getColor("splash_color", context); + var hoverColor = control.getColor("hover_color", context); + var focusColor = control.getColor("focus_color", context); + var shape = control.getShape("shape", Theme.of(context)); var clipBehavior = - parseClip(control.attrString("clipBehavior"), Clip.none)!; - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - bool autofocus = control.attrBool("autofocus", false)!; - bool mini = control.attrBool("mini", false)!; - bool? enableFeedback = control.attrBool("enableFeedback"); - var mouseCursor = parseMouseCursor(control.attrString("mouseCursor")); - bool disabled = control.isDisabled || parentDisabled; + parseClip(control.getString("clip_behavior"), Clip.none)!; + var autofocus = control.getBool("autofocus", false)!; + var mini = control.getBool("mini", false)!; + var enableFeedback = control.getBool("enable_feedback"); + var mouseCursor = control.getMouseCursor("mouse_cursor"); - Function()? onPressed = disabled + Function()? onPressed = control.disabled ? null : () { - debugPrint("FloatingActionButtonControl ${control.id} clicked!"); - if (url != "") { + if (url != null) { openWebBrowser(url, webWindowName: urlTarget); } - backend.triggerControlEvent(control.id, "click"); + control.triggerEvent("click"); }; - if (text == null && icon == null && contentCtrls.isEmpty) { + if (icon == null && content == null) { return const ErrorControl( - "FloatingActionButton has nothing to display. Provide at minimum one of these: text, icon, content"); + "FloatingActionButton has nothing to display. Provide at minimum one of these: icon, content"); } + var child = icon != null + ? content == null + ? icon + : null + : content; Widget button; - if (contentCtrls.isNotEmpty) { + if (child != null) { button = FloatingActionButton( heroTag: control.id, autofocus: autofocus, onPressed: onPressed, mouseCursor: mouseCursor, - backgroundColor: bgColor, + backgroundColor: bgcolor, foregroundColor: foregroundColor, hoverColor: hoverColor, splashColor: splashColor, @@ -92,60 +85,16 @@ class FloatingActionButtonControl extends StatelessWidget { focusColor: focusColor, shape: shape, mini: mini, - child: createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: parentAdaptive)); - } else if (icon != null && text == null) { - button = FloatingActionButton( - heroTag: control.id, - autofocus: autofocus, - onPressed: onPressed, - mouseCursor: mouseCursor, - backgroundColor: bgColor, - foregroundColor: foregroundColor, - hoverColor: hoverColor, - splashColor: splashColor, - elevation: elevation, - disabledElevation: disabledElevation, - focusElevation: focusElevation, - hoverElevation: hoverElevation, - highlightElevation: highlightElevation, - enableFeedback: enableFeedback, - clipBehavior: clipBehavior, - focusColor: focusColor, - shape: shape, - mini: mini, - child: Icon(icon)); - } else if (icon == null && text != null) { - button = FloatingActionButton( - heroTag: control.id, - autofocus: autofocus, - onPressed: onPressed, - mouseCursor: mouseCursor, - backgroundColor: bgColor, - foregroundColor: foregroundColor, - hoverColor: hoverColor, - splashColor: splashColor, - elevation: elevation, - disabledElevation: disabledElevation, - focusElevation: focusElevation, - hoverElevation: hoverElevation, - highlightElevation: highlightElevation, - enableFeedback: enableFeedback, - clipBehavior: clipBehavior, - focusColor: focusColor, - shape: shape, - mini: mini, - child: Text(text), - ); - } else if (icon != null && text != null) { + child: child); + } else if (content != null) { button = FloatingActionButton.extended( heroTag: control.id, autofocus: autofocus, onPressed: onPressed, mouseCursor: mouseCursor, - label: Text(text), - icon: Icon(icon), - backgroundColor: bgColor, + label: content, + icon: icon, + backgroundColor: bgcolor, foregroundColor: foregroundColor, hoverColor: hoverColor, splashColor: splashColor, @@ -161,9 +110,9 @@ class FloatingActionButtonControl extends StatelessWidget { ); } else { return const ErrorControl( - "FloatingActionButton has nothing to display. Provide at minimum one of these: text, icon, content"); + "FloatingActionButton has nothing to display. Provide at minimum icon or content."); } - return constrainedControl(context, button, parent, control); + return ConstrainedControl(control: control, child: button); } } diff --git a/packages/flet/lib/src/controls/gesture_detector.dart b/packages/flet/lib/src/controls/gesture_detector.dart index 0d4b91d35..78ee26226 100644 --- a/packages/flet/lib/src/controls/gesture_detector.dart +++ b/packages/flet/lib/src/controls/gesture_detector.dart @@ -1,32 +1,23 @@ import 'dart:async'; -import 'dart:convert'; +import 'package:flet/src/utils/events.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; +import '../utils/gesture_detector.dart'; +import '../utils/misc.dart'; import '../utils/mouse.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; class GestureDetectorControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const GestureDetectorControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + GestureDetectorControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _GestureDetectorControlState(); @@ -62,85 +53,61 @@ class _GestureDetectorControlState extends State { Widget build(BuildContext context) { debugPrint("GestureDetector build: ${widget.control.id}"); - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); - bool disabled = widget.control.isDisabled || widget.parentDisabled; + var content = widget.control.buildWidget("content"); - void sendEvent(String eventName, [dynamic eventData = ""]) { - var d = ""; - if (eventData is String) { - d = eventData; - } else if (eventData is Map) { - d = json.encode(eventData); - } - - debugPrint("GestureDetector ${widget.control.id} $eventName"); - widget.backend.triggerControlEvent(widget.control.id, eventName, d); - } - - var onHover = widget.control.attrBool("onHover", false)!; - var onEnter = widget.control.attrBool("onEnter", false)!; - var onExit = widget.control.attrBool("onExit", false)!; - var onTap = widget.control.attrBool("onTap", false)!; - var onTapDown = widget.control.attrBool("onTapDown", false)!; - var onTapUp = widget.control.attrBool("onTapUp", false)!; - var onSecondaryTap = widget.control.attrBool("onSecondaryTap", false)!; + var onHover = widget.control.getBool("on_hover", false)!; + var onEnter = widget.control.getBool("on_enter", false)!; + var onExit = widget.control.getBool("on_exit", false)!; + var onTap = widget.control.getBool("on_tap", false)!; + var onTapDown = widget.control.getBool("on_tap_down", false)!; + var onTapUp = widget.control.getBool("on_tap_up", false)!; + var onSecondaryTap = widget.control.getBool("on_secondary_tap", false)!; var onSecondaryTapDown = - widget.control.attrBool("onSecondaryTapDown", false)!; - var onSecondaryTapUp = widget.control.attrBool("onSecondaryTapUp", false)!; - var onLongPressStart = widget.control.attrBool("onLongPressStart", false)!; - var onLongPressEnd = widget.control.attrBool("onLongPressEnd", false)!; + widget.control.getBool("on_secondary_tap_down", false)!; + var onSecondaryTapUp = + widget.control.getBool("on_secondary_tap_up", false)!; + var onLongPressStart = + widget.control.getBool("on_long_press_start", false)!; + var onLongPressEnd = widget.control.getBool("on_long_press_end", false)!; var onSecondaryLongPressStart = - widget.control.attrBool("onSecondaryLongPressStart", false)!; + widget.control.getBool("on_secondary_long_press_start", false)!; var onSecondaryLongPressEnd = - widget.control.attrBool("onSecondaryLongPressEnd", false)!; - var onDoubleTap = widget.control.attrBool("onDoubleTap", false)!; - var onDoubleTapDown = widget.control.attrBool("onDoubleTapDown", false)!; + widget.control.getBool("on_secondary_long_press_end", false)!; + var onDoubleTap = widget.control.getBool("on_double_tap", false)!; + var onDoubleTapDown = widget.control.getBool("on_double_tap_down", false)!; var onHorizontalDragStart = - widget.control.attrBool("onHorizontalDragStart", false)!; + widget.control.getBool("on_horizontal_drag_start", false)!; var onHorizontalDragUpdate = - widget.control.attrBool("onHorizontalDragUpdate", false)!; + widget.control.getBool("on_horizontal_drag_update", false)!; var onHorizontalDragEnd = - widget.control.attrBool("onHorizontalDragEnd", false)!; + widget.control.getBool("on_horizontal_drag_end", false)!; var onVerticalDragStart = - widget.control.attrBool("onVerticalDragStart", false)!; + widget.control.getBool("on_vertical_drag_start", false)!; var onVerticalDragUpdate = - widget.control.attrBool("onVerticalDragUpdate", false)!; + widget.control.getBool("on_vertical_drag_update", false)!; var onVerticalDragEnd = - widget.control.attrBool("onVerticalDragEnd", false)!; - var onPanStart = widget.control.attrBool("onPanStart", false)!; - var onPanUpdate = widget.control.attrBool("onPanUpdate", false)!; - var onPanEnd = widget.control.attrBool("onPanEnd", false)!; - var onScaleStart = widget.control.attrBool("onScaleStart", false)!; - var onScaleUpdate = widget.control.attrBool("onScaleUpdate", false)!; - var onScaleEnd = widget.control.attrBool("onScaleEnd", false)!; - var onMultiTap = widget.control.attrBool("onMultiTap", false)!; - var onMultiLongPress = widget.control.attrBool("onMultiLongPress", false)!; - var multiTapTouches = widget.control.attrInt("multiTapTouches", 0)!; - var onScroll = widget.control.attrBool("onScroll", false)!; - - var content = contentCtrls.isNotEmpty - ? createControl(widget.control, contentCtrls.first.id, disabled, - parentAdaptive: - widget.control.attrBool("adaptive") ?? widget.parentAdaptive) - : null; + widget.control.getBool("on_vertical_drag_end", false)!; + var onPanStart = widget.control.getBool("on_pan_start", false)!; + var onPanUpdate = widget.control.getBool("on_pan_update", false)!; + var onPanEnd = widget.control.getBool("on_pan_end", false)!; + var onScaleStart = widget.control.getBool("on_scale_start", false)!; + var onScaleUpdate = widget.control.getBool("on_scale_update", false)!; + var onScaleEnd = widget.control.getBool("on_scale_end", false)!; + var onMultiTap = widget.control.getBool("on_multi_tap", false)!; + var onMultiLongPress = + widget.control.getBool("on_multi_long_press", false)!; + var multiTapTouches = widget.control.getInt("multi_tap_touches", 0)!; + var onScroll = widget.control.getBool("on_scroll", false)!; Widget? result = content; - var dragInterval = widget.control.attrInt("dragInterval", 0)!; + var dragInterval = widget.control.getInt("drag_interval", 0)!; void handlePanStart(DragStartDetails details) { _panX = details.localPosition.dx; _panY = details.localPosition.dy; if (onPanStart) { - sendEvent("pan_start", { - "kind": details.kind?.name, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - "ts": details.sourceTimeStamp?.inMilliseconds - }); + widget.control.triggerEvent("pan_start", details.toMap()); } } @@ -148,20 +115,9 @@ class _GestureDetectorControlState extends State { var now = DateTime.now().millisecondsSinceEpoch; if (now - _panTimestamp > dragInterval) { _panTimestamp = now; - var dx = details.localPosition.dx - _panX; - var dy = details.localPosition.dy - _panY; + widget.control.triggerEvent("pan_update", details.toMap(_panX, _panY)); _panX = details.localPosition.dx; _panY = details.localPosition.dy; - sendEvent("pan_update", { - "dx": dx, - "dy": dy, - "pd": details.primaryDelta, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - "ts": details.sourceTimeStamp?.inMilliseconds - }); } } @@ -169,14 +125,7 @@ class _GestureDetectorControlState extends State { _hDragX = details.localPosition.dx; _hDragY = details.localPosition.dy; if (onHorizontalDragStart) { - sendEvent("horizontal_drag_start", { - "kind": details.kind?.name, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - "ts": details.sourceTimeStamp?.inMilliseconds - }); + widget.control.triggerEvent("horizontal_drag_start", details.toMap()); } } @@ -184,20 +133,10 @@ class _GestureDetectorControlState extends State { var now = DateTime.now().millisecondsSinceEpoch; if (now - _hDragTimestamp > dragInterval) { _hDragTimestamp = now; - var dx = details.localPosition.dx - _hDragX; - var dy = details.localPosition.dy - _hDragY; + widget.control.triggerEvent( + "horizontal_drag_update", details.toMap(_hDragX, _hDragY)); _hDragX = details.localPosition.dx; _hDragY = details.localPosition.dy; - sendEvent("horizontal_drag_update", { - "dx": dx, - "dy": dy, - "pd": details.primaryDelta, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - "ts": details.sourceTimeStamp?.inMilliseconds - }); } } @@ -205,14 +144,7 @@ class _GestureDetectorControlState extends State { _vDragX = details.localPosition.dx; _vDragY = details.localPosition.dy; if (onVerticalDragStart) { - sendEvent("vertical_drag_start", { - "kind": details.kind?.name, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - "ts": details.sourceTimeStamp?.inMilliseconds - }); + widget.control.triggerEvent("vertical_drag_start", details.toMap()); } } @@ -220,37 +152,20 @@ class _GestureDetectorControlState extends State { var now = DateTime.now().millisecondsSinceEpoch; if (now - _vDragTimestamp > dragInterval) { _vDragTimestamp = now; - var dx = details.localPosition.dx - _vDragX; - var dy = details.localPosition.dy - _vDragY; + widget.control.triggerEvent( + "vertical_drag_update", details.toMap(_vDragX, _vDragY)); _vDragX = details.localPosition.dx; _vDragY = details.localPosition.dy; - sendEvent("vertical_drag_update", { - "dx": dx, - "dy": dy, - "pd": details.primaryDelta, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - "ts": details.sourceTimeStamp?.inMilliseconds - }); } } - var hoverInterval = widget.control.attrInt("hoverInterval", 0)!; + var hoverInterval = widget.control.getInt("hover_interval", 0)!; void handleEnter(PointerEnterEvent details) { _hoverX = details.localPosition.dx; _hoverY = details.localPosition.dy; if (onEnter) { - sendEvent("enter", { - "ts": details.timeStamp.inMilliseconds, - "kind": details.kind.name, - "gx": details.position.dx, - "gy": details.position.dy, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy - }); + widget.control.triggerEvent("enter", details.toMap()); } } @@ -258,20 +173,9 @@ class _GestureDetectorControlState extends State { var now = DateTime.now().millisecondsSinceEpoch; if (now - _hoverTimestamp > hoverInterval) { _hoverTimestamp = now; - var dx = details.localPosition.dx - _hoverX; - var dy = details.localPosition.dy - _hoverY; + widget.control.triggerEvent("hover", details.toMap(_hoverX, _hoverY)); _hoverX = details.localPosition.dx; _hoverY = details.localPosition.dy; - sendEvent("hover", { - "ts": details.timeStamp.inMilliseconds, - "kind": details.kind.name, - "gx": details.position.dx, - "gy": details.position.dy, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "dx": dx, - "dy": dy, - }); } } @@ -302,137 +206,74 @@ class _GestureDetectorControlState extends State { ? GestureDetector( behavior: HitTestBehavior.translucent, excludeFromSemantics: - widget.control.attrBool("excludeFromSemantics", false)!, + widget.control.getBool("exclude_from_semantics", false)!, trackpadScrollCausesScale: - widget.control.attrBool("trackpadScrollCausesScale", false)!, + widget.control.getBool("trackpad_scroll_causes_scale", false)!, supportedDevices: () { - var supportedDevicesString = - widget.control.attrString("allowedDevices"); - var supportedDevicesJson = supportedDevicesString != null - ? jsonDecode(supportedDevicesString) - : null; - if (supportedDevicesJson != null && - (supportedDevicesJson is Iterable && - supportedDevicesJson is! Map)) { - return supportedDevicesJson - .map((d) => parsePointerDeviceKind(d)) - .nonNulls - .toSet(); - } - return null; + var supportedDevices = + widget.control.get>("allowed_devices"); + return supportedDevices + ?.map((d) => parsePointerDeviceKind(d)) + .nonNulls + .toSet(); }(), - onTap: onTap - ? () { - sendEvent("tap"); - } - : null, + onTap: onTap ? () => widget.control.triggerEvent("tap") : null, onTapDown: onTapDown - ? (details) { - sendEvent("tap_down", { - "kind": details.kind?.name, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - }); + ? (TapDownDetails details) { + widget.control.triggerEvent("tap_down", details.toMap()); } : null, onTapUp: onTapUp - ? (details) { - sendEvent("tap_up", { - "kind": details.kind.name, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - }); + ? (TapUpDetails details) { + widget.control.triggerEvent("tap_up", details.toMap()); } : null, onSecondaryTap: onSecondaryTap - ? () { - sendEvent("secondary_tap"); - } + ? () => widget.control.triggerEvent("secondary_tap") : null, onSecondaryTapDown: onSecondaryTapDown - ? (details) { - sendEvent("secondary_tap_down", { - "kind": details.kind?.name, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - }); + ? (TapDownDetails details) { + widget.control + .triggerEvent("secondary_tap_down", details.toMap()); } : null, onSecondaryTapUp: onSecondaryTapUp - ? (details) { - sendEvent("secondary_tap_up", { - "kind": details.kind.name, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - }); + ? (TapUpDetails details) { + widget.control + .triggerEvent("secondary_tap_up", details.toMap()); } : null, onLongPressStart: onLongPressStart - ? (details) { - sendEvent("long_press_start", { - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - }); + ? (LongPressStartDetails details) { + widget.control + .triggerEvent("long_press_start", details.toMap()); } : null, onLongPressEnd: onLongPressEnd - ? (details) { - sendEvent("long_press_end", { - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - "vx": details.velocity.pixelsPerSecond.dx, - "vy": details.velocity.pixelsPerSecond.dy - }); + ? (LongPressEndDetails details) { + widget.control + .triggerEvent("long_press_end", details.toMap()); } : null, onSecondaryLongPressStart: onSecondaryLongPressStart - ? (details) { - sendEvent("secondary_long_press_start", { - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - }); + ? (LongPressStartDetails details) { + widget.control.triggerEvent( + "secondary_long_press_start", details.toMap()); } : null, onSecondaryLongPressEnd: onSecondaryLongPressEnd - ? (details) { - sendEvent("secondary_long_press_end", { - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - "vx": details.velocity.pixelsPerSecond.dx, - "vy": details.velocity.pixelsPerSecond.dy - }); + ? (LongPressEndDetails details) { + widget.control.triggerEvent( + "secondary_long_press_end", details.toMap()); } : null, onDoubleTap: onDoubleTap - ? () { - sendEvent("double_tap"); - } + ? () => widget.control.triggerEvent("double_tap") : null, onDoubleTapDown: onDoubleTapDown - ? (details) { - sendEvent("double_tap_down", { - "kind": details.kind?.name, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "gx": details.globalPosition.dx, - "gy": details.globalPosition.dy, - }); + ? (TapDownDetails details) { + widget.control + .triggerEvent("double_tap_down", details.toMap()); } : null, onHorizontalDragStart: @@ -445,29 +286,21 @@ class _GestureDetectorControlState extends State { } : null, onHorizontalDragEnd: onHorizontalDragEnd - ? (details) { - sendEvent("horizontal_drag_end", { - "pv": details.primaryVelocity, - "vx": details.velocity.pixelsPerSecond.dx, - "vy": details.velocity.pixelsPerSecond.dy - }); + ? (DragEndDetails details) { + widget.control + .triggerEvent("horizontal_drag_end", details.toMap()); } : null, onVerticalDragStart: (onVerticalDragStart || onVerticalDragUpdate) ? handleVerticalDragStart : null, onVerticalDragUpdate: onVerticalDragUpdate - ? (details) { - handleVerticalDragUpdate(details); - } + ? (details) => handleVerticalDragUpdate(details) : null, onVerticalDragEnd: onVerticalDragEnd ? (details) { - sendEvent("vertical_drag_end", { - "pv": details.primaryVelocity, - "vx": details.velocity.pixelsPerSecond.dx, - "vy": details.velocity.pixelsPerSecond.dy - }); + widget.control + .triggerEvent("vertical_drag_end", details.toMap()); } : null, onPanStart: (onPanStart || onPanUpdate) ? handlePanStart : null, @@ -477,49 +310,24 @@ class _GestureDetectorControlState extends State { } : null, onPanEnd: onPanEnd - ? (details) { - sendEvent("pan_end", { - "pv": details.primaryVelocity, - "vx": details.velocity.pixelsPerSecond.dx, - "vy": details.velocity.pixelsPerSecond.dy - }); + ? (DragEndDetails details) { + widget.control.triggerEvent("pan_end", details.toMap()); } : null, onScaleStart: onScaleStart - ? (details) { - sendEvent("scale_start", { - "fpx": details.focalPoint.dx, - "fpy": details.focalPoint.dy, - "lfpx": details.localFocalPoint.dx, - "lfpy": details.localFocalPoint.dy, - "pc": details.pointerCount - }); + ? (ScaleStartDetails details) { + widget.control.triggerEvent("scale_start", details.toMap()); } : null, onScaleUpdate: onScaleUpdate - ? (details) { - sendEvent("scale_update", { - "fpx": details.focalPoint.dx, - "fpy": details.focalPoint.dy, - "fpdx": details.focalPointDelta.dx, - "fpdy": details.focalPointDelta.dy, - "lfpx": details.localFocalPoint.dx, - "lfpy": details.localFocalPoint.dy, - "pc": details.pointerCount, - "hs": details.horizontalScale, - "vs": details.verticalScale, - "s": details.scale, - "r": details.rotation, - }); + ? (ScaleUpdateDetails details) { + widget.control + .triggerEvent("scale_update", details.toMap()); } : null, onScaleEnd: onScaleEnd - ? (details) { - sendEvent("scale_end", { - "pc": details.pointerCount, - "vx": details.velocity.pixelsPerSecond.dx, - "vy": details.velocity.pixelsPerSecond.dy - }); + ? (ScaleEndDetails details) { + widget.control.triggerEvent("scale_end", details.toMap()); } : null, child: result) @@ -536,13 +344,14 @@ class _GestureDetectorControlState extends State { instance.minNumberOfTouches = multiTapTouches; instance.onMultiTap = (correctNumberOfTouches) { if (onMultiTap) { - sendEvent("multi_tap", correctNumberOfTouches.toString()); + widget.control.triggerEvent( + "multi_tap", {"ct": correctNumberOfTouches}); } if (onMultiLongPress) { if (correctNumberOfTouches) { _debounce = Timer(const Duration(milliseconds: 1000), () { - sendEvent("multi_long_press"); + widget.control.triggerEvent("multi_long_press"); }); } else if (_debounce?.isActive ?? false) { _debounce!.cancel(); @@ -561,40 +370,22 @@ class _GestureDetectorControlState extends State { behavior: HitTestBehavior.translucent, onPointerSignal: (details) { if (details is PointerScrollEvent) { - sendEvent("scroll", { - "gx": details.position.dx, - "gy": details.position.dy, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy, - "dx": details.scrollDelta.dx, - "dy": details.scrollDelta.dy, - }); + widget.control.triggerEvent("scroll", details.toMap()); } }, - child: result, - ) + child: result) : result; - var mouseCursor = widget.control.attrString("mouseCursor"); + var mouseCursor = + parseMouseCursor(widget.control.getString("mouse_cursor")); result = ((mouseCursor != null) || onHover || onEnter || onExit) ? MouseRegion( - cursor: parseMouseCursor(mouseCursor, MouseCursor.defer)!, - onHover: onHover - ? (details) { - handleHover(details); - } - : null, + cursor: mouseCursor ?? MouseCursor.defer, + onHover: onHover ? (details) => handleHover(details) : null, onEnter: (onEnter || onHover) ? handleEnter : null, onExit: onExit - ? (details) { - sendEvent("exit", { - "ts": details.timeStamp.inMilliseconds, - "kind": details.kind.name, - "gx": details.position.dx, - "gy": details.position.dy, - "lx": details.localPosition.dx, - "ly": details.localPosition.dy - }); + ? (PointerExitEvent details) { + widget.control.triggerEvent("exit", details.toMap()); } : null, child: result, @@ -606,59 +397,6 @@ class _GestureDetectorControlState extends State { "GestureDetector should have at least one event handler defined"); } - return constrainedControl(context, result, widget.parent, widget.control); - } -} - -class MultiTouchGestureRecognizer extends MultiTapGestureRecognizer { - late MultiTouchGestureRecognizerCallback onMultiTap; - var numberOfTouches = 0; - int minNumberOfTouches = 0; - - MultiTouchGestureRecognizer() { - super.onTapDown = (pointer, details) => addTouch(pointer, details); - super.onTapUp = (pointer, details) => removeTouch(pointer, details); - super.onTapCancel = (pointer) => cancelTouch(pointer); - super.onTap = (pointer) => captureDefaultTap(pointer); - } - - void addTouch(int pointer, TapDownDetails details) { - //debugPrint("Add touch: $pointer"); - numberOfTouches++; - if (numberOfTouches == minNumberOfTouches) { - onMultiTap(true); - numberOfTouches = 0; - } - } - - void removeTouch(int pointer, TapUpDetails details) { - onRemoveTouch(pointer); + return ConstrainedControl(control: widget.control, child: result); } - - void cancelTouch(int pointer) { - onRemoveTouch(pointer); - } - - void onRemoveTouch(int pointer) { - //debugPrint("Remove touch: $pointer"); - onMultiTap(false); - numberOfTouches = 0; - } - - void captureDefaultTap(int pointer) {} - - @override - set onTapDown(onTapDown) {} - - @override - set onTapUp(onTapUp) {} - - @override - set onTapCancel(onTapCancel) {} - - @override - set onTap(onTap) {} } - -typedef MultiTouchGestureRecognizerCallback = void Function( - bool correctNumberOfTouches); diff --git a/packages/flet/lib/src/controls/grid_view.dart b/packages/flet/lib/src/controls/grid_view.dart index 2b28181a1..4c0650617 100644 --- a/packages/flet/lib/src/controls/grid_view.dart +++ b/packages/flet/lib/src/controls/grid_view.dart @@ -1,29 +1,20 @@ import 'package:flutter/widgets.dart'; -import '../flet_control_backend.dart'; +import '../controls/control_widget.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/edge_insets.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; import 'scroll_notification_control.dart'; import 'scrollable_control.dart'; class GridViewControl extends StatefulWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final bool? parentAdaptive; - final List children; - final FletControlBackend backend; - - const GridViewControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + + GridViewControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _GridViewControlState(); @@ -48,31 +39,24 @@ class _GridViewControlState extends State { Widget build(BuildContext context) { debugPrint("GridViewControl build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - - final horizontal = widget.control.attrBool("horizontal", false)!; - final runsCount = widget.control.attrInt("runsCount", 1)!; - final maxExtent = widget.control.attrDouble("maxExtent"); - final spacing = widget.control.attrDouble("spacing", 10)!; - final semanticChildCount = widget.control.attrInt("semanticChildCount"); - final runSpacing = widget.control.attrDouble("runSpacing", 10)!; - final padding = parseEdgeInsets(widget.control, "padding"); - final childAspectRatio = widget.control.attrDouble("childAspectRatio", 1)!; - final reverse = widget.control.attrBool("reverse", false)!; - final cacheExtent = widget.control.attrDouble("cacheExtent"); + final horizontal = widget.control.getBool("horizontal", false)!; + final runsCount = widget.control.getInt("runs_count", 1)!; + final maxExtent = widget.control.getDouble("max_extent"); + final spacing = widget.control.getDouble("spacing", 10)!; + final semanticChildCount = widget.control.getInt("semantic_child_count"); + final runSpacing = widget.control.getDouble("run_spacing", 10)!; + final padding = widget.control.getPadding("padding"); + final childAspectRatio = widget.control.getDouble("child_aspect_ratio", 1)!; + final reverse = widget.control.getBool("reverse", false)!; + final cacheExtent = widget.control.getDouble("cache_extent"); var clipBehavior = - parseClip(widget.control.attrString("clipBehavior"), Clip.hardEdge)!; - - List visibleControls = - widget.children.where((c) => c.isVisible).toList(); + widget.control.getClipBehavior("clip_behavior", Clip.hardEdge)!; var gridView = LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - debugPrint("GridView constraints.maxWidth: ${constraints.maxWidth}"); - debugPrint("GridView constraints.maxHeight: ${constraints.maxHeight}"); + // debugPrint("GridView constraints.maxWidth: ${constraints.maxWidth}"); + // debugPrint("GridView constraints.maxHeight: ${constraints.maxHeight}"); var shrinkWrap = (!horizontal && constraints.maxHeight == double.infinity) || @@ -91,7 +75,7 @@ class _GridViewControlState extends State { childAspectRatio: childAspectRatio); var buildControlsOnDemand = - widget.control.attrBool("buildControlsOnDemand", true)!; + widget.control.getBool("build_controls_on_demand", true)!; Widget child = !buildControlsOnDemand ? GridView( scrollDirection: horizontal ? Axis.horizontal : Axis.vertical, @@ -103,10 +87,7 @@ class _GridViewControlState extends State { shrinkWrap: shrinkWrap, padding: padding, gridDelegate: gridDelegate, - children: visibleControls - .map((c) => createControl(widget.control, c.id, disabled, - parentAdaptive: adaptive)) - .toList(), + children: widget.control.buildWidgets("controls"), ) : GridView.builder( scrollDirection: horizontal ? Axis.horizontal : Axis.vertical, @@ -118,11 +99,10 @@ class _GridViewControlState extends State { shrinkWrap: shrinkWrap, padding: padding, gridDelegate: gridDelegate, - itemCount: visibleControls.length, + itemCount: widget.control.children("controls").length, itemBuilder: (context, index) { - return createControl( - widget.control, visibleControls[index].id, disabled, - parentAdaptive: adaptive); + return ControlWidget( + control: widget.control.children("controls")[index]); }, ); @@ -130,19 +110,17 @@ class _GridViewControlState extends State { control: widget.control, scrollDirection: horizontal ? Axis.horizontal : Axis.vertical, scrollController: _controller, - backend: widget.backend, - parentAdaptive: adaptive, child: child); - if (widget.control.attrBool("onScroll", false)!) { - child = ScrollNotificationControl( - control: widget.control, backend: widget.backend, child: child); + if (widget.control.getBool("on_scroll", false)!) { + child = + ScrollNotificationControl(control: widget.control, child: child); } return child; }, ); - return constrainedControl(context, gridView, widget.parent, widget.control); + return ConstrainedControl(control: widget.control, child: gridView); } } diff --git a/packages/flet/lib/src/controls/haptic_feedback.dart b/packages/flet/lib/src/controls/haptic_feedback.dart deleted file mode 100644 index 43dc53e32..000000000 --- a/packages/flet/lib/src/controls/haptic_feedback.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; - -import '../flet_control_backend.dart'; -import '../models/control.dart'; - -class HapticFeedbackControl extends StatefulWidget { - final Control? parent; - final Control control; - final Widget? nextChild; - final FletControlBackend backend; - - const HapticFeedbackControl( - {super.key, - required this.parent, - required this.control, - required this.nextChild, - required this.backend}); - - @override - State createState() => _HapticFeedbackControlState(); -} - -class _HapticFeedbackControlState extends State { - @override - void deactivate() { - widget.backend.unsubscribeMethods(widget.control.id); - super.deactivate(); - } - - @override - Widget build(BuildContext context) { - debugPrint("HapticFeedback build: ${widget.control.id}"); - - widget.backend.subscribeMethods(widget.control.id, - (methodName, args) async { - switch (methodName) { - case "heavy_impact": - HapticFeedback.heavyImpact(); - break; - case "light_impact": - HapticFeedback.lightImpact(); - break; - case "medium_impact": - HapticFeedback.mediumImpact(); - break; - case "vibrate": - HapticFeedback.vibrate(); - break; - } - return null; - }); - - return widget.nextChild ?? const SizedBox.shrink(); - } -} diff --git a/packages/flet/lib/src/controls/icon.dart b/packages/flet/lib/src/controls/icon.dart index bdab3126a..d316978ff 100644 --- a/packages/flet/lib/src/controls/icon.dart +++ b/packages/flet/lib/src/controls/icon.dart @@ -1,37 +1,36 @@ -import 'package:flet/src/utils/box.dart'; import 'package:flutter/material.dart'; import '../models/control.dart'; +import '../utils/box.dart'; +import '../utils/colors.dart'; import '../utils/icons.dart'; import '../utils/images.dart'; -import 'create_control.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; class IconControl extends StatelessWidget { - final Control? parent; final Control control; - const IconControl({super.key, required this.parent, required this.control}); + const IconControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("Icon build: ${control.id}"); - return constrainedControl( - context, - Icon( - parseIcon(control.attrString("name", "")!), - size: control.attrDouble("size"), - color: control.attrColor("color", context), - blendMode: parseBlendMode(control.attrString("blendMode")), - semanticLabel: control.attrString("semanticsLabel"), - applyTextScaling: control.attrBool("applyTextScaling"), - fill: control.attrDouble("fill"), - grade: control.attrDouble("grade"), - weight: control.attrDouble("weight"), - opticalSize: control.attrDouble("opticalSize"), - shadows: parseBoxShadow(Theme.of(context), control, "shadows"), - ), - parent, - control); + return ConstrainedControl( + control: control, + child: Icon( + parseIcon(control.getString("name", "")!), + size: control.getDouble("size"), + color: control.getColor("color", context), + blendMode: control.getBlendMode("blend_mode"), + semanticLabel: control.getString("semantics_label"), + applyTextScaling: control.getBool("apply_text_scaling"), + fill: control.getDouble("fill"), + grade: control.getDouble("grade"), + weight: control.getDouble("weight"), + opticalSize: control.getDouble("optical_size"), + shadows: control.getBoxShadows("shadows", Theme.of(context)), + )); } } diff --git a/packages/flet/lib/src/controls/icon_button.dart b/packages/flet/lib/src/controls/icon_button.dart index 82e635815..67957d729 100644 --- a/packages/flet/lib/src/controls/icon_button.dart +++ b/packages/flet/lib/src/controls/icon_button.dart @@ -1,36 +1,14 @@ +import 'package:flet/flet.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; -import '../models/control.dart'; -import '../utils/alignment.dart'; -import '../utils/box.dart'; -import '../utils/buttons.dart'; -import '../utils/edge_insets.dart'; -import '../utils/icons.dart'; -import '../utils/launch_url.dart'; -import '../utils/mouse.dart'; -import '../utils/theme.dart'; -import 'create_control.dart'; +import 'control_widget.dart'; import 'cupertino_button.dart'; -import 'error.dart'; -import 'flet_store_mixin.dart'; class IconButtonControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - - const IconButtonControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + + IconButtonControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _IconButtonControlState(); @@ -39,25 +17,35 @@ class IconButtonControl extends StatefulWidget { class _IconButtonControlState extends State with FletStoreMixin { late final FocusNode _focusNode; - String? _lastFocusValue; @override void initState() { super.initState(); _focusNode = FocusNode(); _focusNode.addListener(_onFocusChange); + widget.control.addInvokeMethodListener(_invokeMethod); } @override void dispose() { _focusNode.removeListener(_onFocusChange); _focusNode.dispose(); + widget.control.removeInvokeMethodListener(_invokeMethod); super.dispose(); } void _onFocusChange() { - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); + } + + Future _invokeMethod(String name, dynamic args) async { + debugPrint("IconButton.$name($args)"); + switch (name) { + case "focus": + _focusNode.requestFocus(); + default: + throw Exception("Unknown IconButton method: $name"); + } } @override @@ -65,76 +53,63 @@ class _IconButtonControlState extends State debugPrint("IconButton build: ${widget.control.id}"); return withPagePlatform((context, platform) { - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - if (adaptive == true && + if (widget.control.adaptive == true && (platform == TargetPlatform.iOS || platform == TargetPlatform.macOS)) { return CupertinoButtonControl( - control: widget.control, - parentDisabled: widget.parentDisabled, - parentAdaptive: adaptive, - children: widget.children, - backend: widget.backend); + control: widget.control, + ); } - IconData? icon = parseIcon(widget.control.attrString("icon")); - IconData? selectedIcon = - parseIcon(widget.control.attrString("selectedIcon")); - Color? iconColor = widget.control.attrColor("iconColor", context); - Color? highlightColor = - widget.control.attrColor("highlightColor", context); - Color? selectedIconColor = - widget.control.attrColor("selectedIconColor", context); - Color? bgColor = widget.control.attrColor("bgColor", context); - Color? disabledColor = widget.control.attrColor("disabledColor", context); - Color? hoverColor = widget.control.attrColor("hoverColor", context); - Color? splashColor = widget.control.attrColor("splashColor", context); - Color? focusColor = widget.control.attrColor("focusColor", context); - double? iconSize = widget.control.attrDouble("iconSize"); - double? splashRadius = widget.control.attrDouble("splashRadius"); - var padding = parseEdgeInsets(widget.control, "padding"); - var alignment = parseAlignment(widget.control, "alignment"); + var icon = widget.control.get("icon"); + var selectedIcon = widget.control.get("selected_icon"); + var content = widget.control.child("content"); + var iconColor = widget.control.getColor("icon_color", context); + var highlightColor = widget.control.getColor("highlight_color", context); + var selectedIconColor = + widget.control.getColor("selected_icon_color", context); + var bgcolor = widget.control.getColor("bgcolor", context); + var disabledColor = widget.control.getColor("disabled_color", context); + var hoverColor = widget.control.getColor("hover_color", context); + var splashColor = widget.control.getColor("splash_color", context); + var focusColor = widget.control.getColor("focus_color", context); + var iconSize = widget.control.getDouble("icon_size"); + var splashRadius = widget.control.getDouble("splash_radius"); + var padding = widget.control.getEdgeInsets("padding"); + var alignment = widget.control.getAlignment("alignment"); var sizeConstraints = - parseBoxConstraints(widget.control, "sizeConstraints"); - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); - bool autofocus = widget.control.attrBool("autofocus", false)!; - bool enableFeedback = widget.control.attrBool("enableFeedback", true)!; - bool selected = widget.control.attrBool("selected", false)!; - String url = widget.control.attrString("url", "")!; - String? urlTarget = widget.control.attrString("urlTarget"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - var mouseCursor = - parseMouseCursor(widget.control.attrString("mouseCursor")); - var visualDensity = - parseVisualDensity(widget.control.attrString("visualDensity")); - - Function()? onPressed = disabled - ? null - : () { - debugPrint("Button ${widget.control.id} clicked!"); - if (url != "") { + widget.control.getBoxConstraints("size_constraints"); + var autofocus = widget.control.getBool("autofocus", false)!; + var enableFeedback = widget.control.getBool("enable_feedback", true)!; + var selected = widget.control.getBool("selected", false)!; + var url = widget.control.getString("url"); + var urlTarget = widget.control.getString("url_target"); + var mouseCursor = widget.control.getMouseCursor("mouse_cursor"); + var visualDensity = widget.control.getVisualDensity("visual_density"); + + Function()? onPressed = !widget.control.disabled + ? () { + if (url != null) { openWebBrowser(url, webWindowName: urlTarget); } - widget.backend.triggerControlEvent(widget.control.id, "click"); - }; + widget.control.triggerEvent("click"); + } + : null; - Function()? onLongPressHandler = !disabled - ? () => - widget.backend.triggerControlEvent(widget.control.id, "longPress") + Function()? onLongPressHandler = !widget.control.disabled + ? () => widget.control.triggerEvent("long_press") : null; - Function(bool)? onHoverHandler = !disabled - ? (bool hovered) => widget.backend.triggerControlEvent( - widget.control.id, "hover", hovered.toString()) + Function(bool)? onHoverHandler = !widget.control.disabled + ? (bool hovered) => FletBackend.of(context) + .triggerControlEvent(widget.control, "hover", hovered) : null; Widget? button; var theme = Theme.of(context); - - var style = parseButtonStyle(Theme.of(context), widget.control, "style", + var style = parseButtonStyle( + widget.control.get("style"), Theme.of(context), defaultForegroundColor: theme.colorScheme.primary, defaultBackgroundColor: Colors.transparent, defaultOverlayColor: Colors.transparent, @@ -147,7 +122,30 @@ class _IconButtonControlState extends State ? const StadiumBorder() : RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))); - if (icon != null) { + Widget? iconWidget; + if (icon is Control) { + iconWidget = ControlWidget(control: icon); + } else if (icon is String) { + iconWidget = Icon( + widget.control.getIcon("icon"), + color: iconColor, + ); + } else if (content != null) { + iconWidget = ControlWidget(control: content); + } + + Widget? selectedIconWidget; + + if (selectedIcon is Control) { + selectedIconWidget = ControlWidget(control: selectedIcon); + } else if (selectedIcon is String) { + selectedIconWidget = Icon( + widget.control.getIcon("selected_icon"), + color: selectedIconColor, + ); + } + + if (iconWidget != null) { button = IconButton( autofocus: autofocus, focusNode: _focusNode, @@ -160,10 +158,7 @@ class _IconButtonControlState extends State focusColor: focusColor, splashColor: splashColor, splashRadius: splashRadius, - icon: Icon( - icon, - color: iconColor, - ), + icon: iconWidget, iconSize: iconSize, mouseCursor: mouseCursor, visualDensity: visualDensity, @@ -172,57 +167,22 @@ class _IconButtonControlState extends State constraints: sizeConstraints, onLongPress: onLongPressHandler, onHover: onHoverHandler, - selectedIcon: selectedIcon != null - ? Icon(selectedIcon, color: selectedIconColor) - : null, + selectedIcon: selectedIconWidget, onPressed: onPressed); - } else if (contentCtrls.isNotEmpty) { - button = IconButton( - autofocus: autofocus, - focusNode: _focusNode, - highlightColor: highlightColor, - disabledColor: highlightColor, - hoverColor: highlightColor, - enableFeedback: enableFeedback, - padding: padding, - alignment: alignment, - focusColor: focusColor, - splashColor: splashColor, - splashRadius: splashRadius, - onPressed: onPressed, - iconSize: iconSize, - mouseCursor: mouseCursor, - visualDensity: visualDensity, - style: style, - isSelected: selected, - constraints: sizeConstraints, - onLongPress: onLongPressHandler, - onHover: onHoverHandler, - selectedIcon: selectedIcon != null - ? Icon(selectedIcon, color: selectedIconColor) - : null, - icon: createControl(widget.control, contentCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive)); } else { return const ErrorControl( "IconButton must have either icon or a visible content specified."); } - if (bgColor != null) { + if (bgcolor != null) { button = Container( decoration: - ShapeDecoration(color: bgColor, shape: const CircleBorder()), + ShapeDecoration(color: bgcolor, shape: const CircleBorder()), child: button, ); } - var focusValue = widget.control.attrString("focus"); - if (focusValue != null && focusValue != _lastFocusValue) { - _lastFocusValue = focusValue; - _focusNode.requestFocus(); - } - - return constrainedControl(context, button, widget.parent, widget.control); + return ConstrainedControl(control: widget.control, child: button); }); } } diff --git a/packages/flet/lib/src/controls/image.dart b/packages/flet/lib/src/controls/image.dart index 694cb6456..0593903ad 100644 --- a/packages/flet/lib/src/controls/image.dart +++ b/packages/flet/lib/src/controls/image.dart @@ -1,82 +1,69 @@ +import 'dart:typed_data'; + import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/borders.dart'; import '../utils/box.dart'; +import '../utils/colors.dart'; import '../utils/images.dart'; -import 'create_control.dart'; -import 'error.dart'; -import 'flet_store_mixin.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; -class ImageControl extends StatelessWidget with FletStoreMixin { - final Control? parent; - final List children; +class ImageControl extends StatelessWidget { final Control control; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; static const String svgTag = " xmlns=\"http://www.w3.org/2000/svg\""; - const ImageControl( - {super.key, - required this.parent, - required this.children, - required this.control, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const ImageControl({ + super.key, + required this.control, + }); @override Widget build(BuildContext context) { debugPrint("Image build: ${control.id}"); - bool disabled = control.isDisabled || parentDisabled; - var src = control.attrString("src", "")!; - var srcBase64 = control.attrString("srcBase64", "")!; - if (src == "" && srcBase64 == "") { + var src = control.getString("src", "")!; + var srcBase64 = control.getString("src_base64", "")!; + var srcBytes = (control.get("src_bytes") as Uint8List?) ?? Uint8List(0); + if (src == "" && srcBase64 == "" && srcBytes.isEmpty) { return const ErrorControl( - "Image must have either \"src\" or \"src_base64\" specified."); + "Image must have either \"src\" or \"src_base64\" or \"src_bytes\" specified."); } - var errorContentCtrls = - children.where((c) => c.name == "error_content" && c.isVisible); + var errorContent = control.buildWidget("error_content"); - return withPageArgs((context, pageArgs) { - Widget? image = buildImage( - context: context, + Widget? image = buildImage( + context: context, + control: control, + src: src, + srcBase64: srcBase64, + srcBytes: srcBytes, + width: control.getDouble("width"), + height: control.getDouble("height"), + cacheWidth: control.getInt("cache_width"), + cacheHeight: control.getInt("cache_height"), + antiAlias: control.getBool("anti_alias", false)!, + repeat: control.getImageRepeat("repeat", ImageRepeat.noRepeat)!, + fit: control.getBoxFit("fit"), + colorBlendMode: control.getBlendMode("color_blend_mode"), + color: control.getColor("color", context), + semanticsLabel: control.getString("semantics_label"), + gaplessPlayback: control.getBool("gapless_playback"), + excludeFromSemantics: control.getBool("exclude_from_semantics", false)!, + filterQuality: + control.getFilterQuality("filter_quality", FilterQuality.medium)!, + disabled: control.disabled, + errorCtrl: errorContent, + ); + return ConstrainedControl( control: control, - src: src, - srcBase64: srcBase64, - width: control.attrDouble("width"), - height: control.attrDouble("height"), - cacheWidth: control.attrInt("cacheWidth"), - cacheHeight: control.attrInt("cacheHeight"), - antiAlias: control.attrBool("antiAlias", false)!, - repeat: parseImageRepeat( - control.attrString("repeat"), ImageRepeat.noRepeat)!, - fit: parseBoxFit(control.attrString("fit")), - colorBlendMode: parseBlendMode(control.attrString("colorBlendMode")), - color: control.attrColor("color", context), - semanticsLabel: control.attrString("semanticsLabel"), - gaplessPlayback: control.attrBool("gaplessPlayback"), - excludeFromSemantics: control.attrBool("excludeFromSemantics", false)!, - filterQuality: parseFilterQuality( - control.attrString("filterQuality"), FilterQuality.medium)!, - disabled: disabled, - pageArgs: pageArgs, - errorCtrl: errorContentCtrls.isNotEmpty - ? createControl(control, errorContentCtrls.first.id, disabled, - parentAdaptive: control.isAdaptive ?? parentAdaptive) - : null, - ); - return constrainedControl( - context, _clipCorners(image, control), parent, control); - }); + child: _clipCorners(image, control.getBorderRadius("border_radius"))); } - Widget _clipCorners(Widget image, Control control) { - var borderRadius = parseBorderRadius(control, "borderRadius"); + Widget _clipCorners(Widget image, BorderRadius? borderRadius) { return borderRadius != null ? ClipRRect( borderRadius: borderRadius, diff --git a/packages/flet/lib/src/controls/interactive_viewer.dart b/packages/flet/lib/src/controls/interactive_viewer.dart index bffdd04c3..f6e7699d0 100644 --- a/packages/flet/lib/src/controls/interactive_viewer.dart +++ b/packages/flet/lib/src/controls/interactive_viewer.dart @@ -1,33 +1,20 @@ -import 'dart:convert'; - +import 'package:flet/src/utils/events.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import '../utils/alignment.dart'; import '../utils/edge_insets.dart'; +import '../utils/misc.dart'; import '../utils/numbers.dart'; -import '../utils/others.dart'; import '../utils/time.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; class InteractiveViewerControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const InteractiveViewerControl( - {super.key, - required this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + InteractiveViewerControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => @@ -48,58 +35,65 @@ class _InteractiveViewerControlState extends State super.initState(); _animationController = AnimationController(vsync: this, duration: Duration.zero); + widget.control.addInvokeMethodListener(_invokeMethod); + } - widget.backend.subscribeMethods(widget.control.id, - (methodName, args) async { - switch (methodName) { - case "zoom": - var factor = parseDouble(args["factor"]); - if (factor != null) { - _transformationController.value = - _transformationController.value.scaled(factor, factor); - } - break; - case "pan": - var dx = parseDouble(args["dx"]); - var dy = parseDouble(args["dy"]); - if (dx != null && dy != null) { - _transformationController.value = - _transformationController.value.clone()..translate(dx, dy); - } - break; - case "reset": - var duration = durationFromString(args["duration"]); - if (duration == null) { - _transformationController.value = Matrix4.identity(); - } else { - _animationController.duration = duration; - _animation = Matrix4Tween( - begin: _transformationController.value, - end: Matrix4.identity(), - ).animate(_animationController) - ..addListener(() { - _transformationController.value = _animation!.value; - }); - _animationController.forward(from: 0); - } - break; - case "save_state": - _savedMatrix = _transformationController.value.clone(); - break; - case "restore_state": - if (_savedMatrix != null) { - _transformationController.value = _savedMatrix!; - } - break; - } - return null; - }); + Future _invokeMethod(String name, dynamic args) async { + debugPrint("OutlinedButton.$name($args)"); + switch (name) { + case "zoom": + var factor = parseDouble(args["factor"]); + if (factor != null) { + _transformationController.value = + _transformationController.value.scaled(factor, factor); + } + break; + case "pan": + var dx = parseDouble(args["dx"]); + if (dx != null) { + _transformationController.value = + _transformationController.value.clone() + ..translate( + dx, + parseDouble(args["dy"], 0)!, + parseDouble(args["dz"], 0)!, + ); + } + break; + case "reset": + var animationDuration = parseDuration(args["animation_duration"]); + if (animationDuration == null) { + _transformationController.value = Matrix4.identity(); + } else { + _animationController.duration = animationDuration; + _animation = Matrix4Tween( + begin: _transformationController.value, + end: Matrix4.identity(), + ).animate(_animationController) + ..addListener(() { + _transformationController.value = _animation!.value; + }); + _animationController.forward(from: 0); + } + break; + case "save_state": + _savedMatrix = _transformationController.value.clone(); + break; + case "restore_state": + if (_savedMatrix != null) { + _transformationController.value = _savedMatrix!; + } + break; + default: + throw Exception("Unknown InteractiveViewer method: $name"); + } } @override void dispose() { _transformationController.dispose(); _animationController.dispose(); + widget.control.removeInvokeMethodListener(_invokeMethod); super.dispose(); } @@ -107,92 +101,53 @@ class _InteractiveViewerControlState extends State Widget build(BuildContext context) { debugPrint("InteractiveViewer build: ${widget.control.id}"); - var contentCtrls = widget.children.where((c) => c.isVisible); - bool? adaptive = widget.control.isAdaptive ?? widget.parentAdaptive; - bool disabled = widget.control.isDisabled || widget.parentDisabled; - + var content = widget.control.buildWidget("content"); var interactiveViewer = InteractiveViewer( transformationController: _transformationController, - panEnabled: widget.control.attrBool("panEnabled", true)!, - scaleEnabled: widget.control.attrBool("scaleEnabled", true)!, + panEnabled: widget.control.getBool("pan_enabled", true)!, + scaleEnabled: widget.control.getBool("scale_enabled", true)!, trackpadScrollCausesScale: - widget.control.attrBool("trackpadScrollCausesScale", false)!, - constrained: widget.control.attrBool("constrained", true)!, - maxScale: widget.control.attrDouble("maxScale", 2.5)!, - minScale: widget.control.attrDouble("minScale", 0.8)!, + widget.control.getBool("trackpad_scroll_causes_scale", false)!, + constrained: widget.control.getBool("constrained", true)!, + maxScale: widget.control.getDouble("max_scale", 2.5)!, + minScale: widget.control.getDouble("min_scale", 0.8)!, interactionEndFrictionCoefficient: widget.control - .attrDouble("interactionEndFrictionCoefficient", 0.0000135)!, - scaleFactor: widget.control.attrDouble("scaleFactor", 200)!, + .getDouble("interaction_end_friction_coefficient", 0.0000135)!, + scaleFactor: widget.control.getDouble("scale_factor", 200)!, clipBehavior: - parseClip(widget.control.attrString("clipBehavior"), Clip.hardEdge)!, - alignment: parseAlignment(widget.control, "alignment"), + parseClip(widget.control.getString("clip_behavior"), Clip.hardEdge)!, + alignment: widget.control.get("alignment"), boundaryMargin: - parseEdgeInsets(widget.control, "boundaryMargin", EdgeInsets.zero)!, - onInteractionStart: !disabled + widget.control.getMargin("boundary_margin", EdgeInsets.zero)!, + onInteractionStart: !widget.control.disabled ? (ScaleStartDetails details) { - debugPrint( - "InteractiveViewer ${widget.control.id} onInteractionStart"); - widget.backend.triggerControlEvent( - widget.control.id, - "interaction_start", - jsonEncode({ - "pc": details.pointerCount, - "fp_x": details.focalPoint.dx, - "fp_y": details.focalPoint.dy, - "lfp_x": details.localFocalPoint.dx, - "lfp_y": details.localFocalPoint.dy - })); + widget.control.triggerEvent("interaction_start", details.toMap()); } : null, - onInteractionEnd: !disabled + onInteractionEnd: !widget.control.disabled ? (ScaleEndDetails details) { - debugPrint( - "InteractiveViewer ${widget.control.id} onInteractionEnd"); - widget.backend.triggerControlEvent( - widget.control.id, - "interaction_end", - jsonEncode({ - "pc": details.pointerCount, - "sv": details.scaleVelocity, - })); + widget.control.triggerEvent("interaction_end", details.toMap()); } : null, - onInteractionUpdate: !disabled + onInteractionUpdate: !widget.control.disabled ? (ScaleUpdateDetails details) { var interactionUpdateInterval = - widget.control.attrInt("interactionUpdateInterval", 200)!; + widget.control.getInt("interaction_update_interval", 200)!; var now = DateTime.now().millisecondsSinceEpoch; if (now - _interactionUpdateTimestamp > interactionUpdateInterval) { - debugPrint( - "InteractiveViewer ${widget.control.id} onInteractionUpdate"); _interactionUpdateTimestamp = now; - widget.backend.triggerControlEvent( - widget.control.id, - "interaction_update", - jsonEncode({ - "pc": details.pointerCount, - "fp_x": details.focalPoint.dx, - "fp_y": details.focalPoint.dy, - "lfp_x": details.localFocalPoint.dx, - "lfp_y": details.localFocalPoint.dy, - "s": details.scale, - "hs": details.horizontalScale, - "vs": details.verticalScale, - "rot": details.rotation, - })); - ; + widget.control + .triggerEvent("interaction_update", details.toMap()); } } : null, - child: contentCtrls.isNotEmpty - ? createControl(widget.control, contentCtrls.first.id, disabled, - parentAdaptive: adaptive) - : const ErrorControl( + child: content ?? + const ErrorControl( "InteractiveViewer.content must be provided and visible"), ); - return constrainedControl( - context, interactiveViewer, widget.parent, widget.control); + return ConstrainedControl( + control: widget.control, child: interactiveViewer); } } diff --git a/packages/flet/lib/src/controls/linechart.dart b/packages/flet/lib/src/controls/linechart.dart deleted file mode 100644 index 421c976cc..000000000 --- a/packages/flet/lib/src/controls/linechart.dart +++ /dev/null @@ -1,561 +0,0 @@ -import 'dart:convert'; - -import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_redux/flutter_redux.dart'; -import 'package:redux/redux.dart'; - -import '../flet_control_backend.dart'; -import '../models/app_state.dart'; -import '../models/control.dart'; -import '../utils/animations.dart'; -import '../utils/borders.dart'; -import '../utils/box.dart'; -import '../utils/charts.dart'; -import '../utils/colors.dart'; -import '../utils/edge_insets.dart'; -import '../utils/gradient.dart'; -import '../utils/numbers.dart'; -import '../utils/text.dart'; -import 'charts.dart'; -import 'create_control.dart'; - -class LineChartDataPointViewModel extends Equatable { - final Control control; - final double x; - final double y; - final String? tooltip; - - const LineChartDataPointViewModel( - {required this.control, - required this.x, - required this.y, - required this.tooltip}); - - static LineChartDataPointViewModel fromStore( - Store store, Control control) { - return LineChartDataPointViewModel( - control: control, - x: control.attrDouble("x")!, - y: control.attrDouble("y")!, - tooltip: control.attrString("tooltip")); - } - - @override - List get props => [control]; -} - -class LineChartDataViewModel extends Equatable { - final Control control; - final List dataPoints; - - const LineChartDataViewModel( - {required this.control, required this.dataPoints}); - - static LineChartDataViewModel fromStore( - Store store, Control control) { - return LineChartDataViewModel( - control: control, - dataPoints: store.state.controls[control.id]!.childIds - .map((childId) => store.state.controls[childId]) - .nonNulls - .where((c) => c.isVisible) - .map((c) => LineChartDataPointViewModel.fromStore(store, c)) - .toList()); - } - - @override - List get props => [control, dataPoints]; -} - -class LineChartEventData extends Equatable { - final String eventType; - final List barSpots; - - const LineChartEventData({required this.eventType, required this.barSpots}); - - Map toJson() => { - 'type': eventType, - 'spots': barSpots, - }; - - @override - List get props => [eventType, barSpots]; -} - -class LineChartEventDataSpot extends Equatable { - final int barIndex; - final int spotIndex; - - const LineChartEventDataSpot( - {required this.barIndex, required this.spotIndex}); - - Map toJson() => { - 'bar_index': barIndex, - 'spot_index': spotIndex, - }; - - @override - List get props => [barIndex, spotIndex]; -} - -class LineChartViewModel extends Equatable { - final Control control; - final ChartAxisViewModel? leftAxis; - final ChartAxisViewModel? topAxis; - final ChartAxisViewModel? rightAxis; - final ChartAxisViewModel? bottomAxis; - final List dataSeries; - - const LineChartViewModel( - {required this.control, - required this.leftAxis, - required this.topAxis, - required this.rightAxis, - required this.bottomAxis, - required this.dataSeries}); - - static LineChartViewModel fromStore( - Store store, Control control, List children) { - var leftAxisCtrls = - children.where((c) => c.type == "axis" && c.name == "l" && c.isVisible); - var topAxisCtrls = - children.where((c) => c.type == "axis" && c.name == "t" && c.isVisible); - var rightAxisCtrls = - children.where((c) => c.type == "axis" && c.name == "r" && c.isVisible); - var bottomAxisCtrls = - children.where((c) => c.type == "axis" && c.name == "b" && c.isVisible); - return LineChartViewModel( - control: control, - leftAxis: leftAxisCtrls.isNotEmpty - ? ChartAxisViewModel.fromStore(store, leftAxisCtrls.first) - : null, - topAxis: topAxisCtrls.isNotEmpty - ? ChartAxisViewModel.fromStore(store, topAxisCtrls.first) - : null, - rightAxis: rightAxisCtrls.isNotEmpty - ? ChartAxisViewModel.fromStore(store, rightAxisCtrls.first) - : null, - bottomAxis: bottomAxisCtrls.isNotEmpty - ? ChartAxisViewModel.fromStore(store, bottomAxisCtrls.first) - : null, - dataSeries: children - .where((c) => c.type == "data" && c.isVisible) - .map((c) => LineChartDataViewModel.fromStore(store, c)) - .toList()); - } - - @override - List get props => - [control, leftAxis, rightAxis, topAxis, bottomAxis, dataSeries]; -} - -class LineChartControl extends StatefulWidget { - final Control? parent; - final Control control; - final List children; - final bool parentDisabled; - final FletControlBackend backend; - - const LineChartControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.backend}); - - @override - State createState() => _LineChartControlState(); -} - -class _LineChartControlState extends State { - LineChartEventData? _eventData; - - @override - Widget build(BuildContext context) { - debugPrint("LineChart build: ${widget.control.id}"); - - var animate = parseAnimation(widget.control, "animate"); - var border = parseBorder(Theme.of(context), widget.control, "border"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - var result = StoreConnector( - distinct: true, - converter: (store) => LineChartViewModel.fromStore( - store, widget.control, widget.children), - builder: (context, viewModel) { - var leftTitles = - getAxisTitles(widget.control, viewModel.leftAxis, disabled); - var topTitles = - getAxisTitles(widget.control, viewModel.topAxis, disabled); - var rightTitles = - getAxisTitles(widget.control, viewModel.rightAxis, disabled); - var bottomTitles = - getAxisTitles(widget.control, viewModel.bottomAxis, disabled); - - var interactive = viewModel.control.attrBool("interactive", true)!; - var pointLineStart = viewModel.control.attrDouble("pointLineStart"); - var pointLineEnd = viewModel.control.attrDouble("pointLineEnd"); - - List barsData = []; - List selectedPoints = []; - - var barIndex = 0; - for (var ds in viewModel.dataSeries) { - var barData = - getBarData(Theme.of(context), widget.control, interactive, ds); - barsData.add(barData); - - if (!interactive) { - var spotIndex = 0; - for (var p in ds.dataPoints) { - if (p.control.attrBool("selected", false)!) { - selectedPoints.add( - LineBarSpot(barData, barIndex, barData.spots[spotIndex])); - } - spotIndex++; - } - } - - barIndex++; - } - - var chart = LineChart( - LineChartData( - backgroundColor: parseColor( - Theme.of(context), widget.control.attrString("bgcolor")), - minX: widget.control.attrDouble("minx"), - maxX: widget.control.attrDouble("maxx"), - minY: widget.control.attrDouble("miny"), - maxY: widget.control.attrDouble("maxy"), - baselineX: widget.control.attrDouble("baselinex"), - baselineY: widget.control.attrDouble("baseliney"), - showingTooltipIndicators: groupBy(selectedPoints, (p) => p.x) - .values - .map((e) => ShowingTooltipIndicators(e)) - .toList(), - titlesData: (leftTitles.sideTitles.showTitles || - topTitles.sideTitles.showTitles || - rightTitles.sideTitles.showTitles || - bottomTitles.sideTitles.showTitles) - ? FlTitlesData( - show: true, - leftTitles: leftTitles, - topTitles: topTitles, - rightTitles: rightTitles, - bottomTitles: bottomTitles, - ) - : const FlTitlesData(show: false), - borderData: border != null - ? FlBorderData(show: true, border: border) - : FlBorderData(show: false), - gridData: parseChartGridData(Theme.of(context), widget.control, - "horizontalGridLines", "verticalGridLines"), - lineBarsData: barsData, - lineTouchData: LineTouchData( - enabled: interactive, - getTouchLineStart: pointLineStart != null - ? (barData, spotIndex) => pointLineStart - : defaultGetTouchLineStart, - getTouchLineEnd: pointLineEnd != null - ? (barData, spotIndex) => pointLineEnd - : defaultGetTouchLineEnd, - getTouchedSpotIndicator: - (LineChartBarData barData, List spotIndexes) { - var barIndex = interactive - ? barsData.indexWhere( - (b) => b == barData.copyWith(showingIndicators: [])) - : barsData.indexWhere((b) => b == barData); - - return spotIndexes.map((index) { - if (barIndex == -1) { - return null; - } - - FlLine? allDotsLine = parseSelectedFlLine( - Theme.of(context), - viewModel.dataSeries[barIndex].control, - "selectedBelowLine", - barData.color, - barData.gradient); - - FlLine? dotLine = parseSelectedFlLine( - Theme.of(context), - viewModel - .dataSeries[barIndex].dataPoints[index].control, - "selectedBelowLine", - barData.color, - barData.gradient); - - return TouchedSpotIndicatorData( - dotLine ?? - allDotsLine ?? - FlLine( - color: defaultGetPointColor( - barData.color, barData.gradient, 0), - strokeWidth: 3), - FlDotData( - show: true, - getDotPainter: (spot, percent, barData, index) { - var allDotsPainter = parseChartSelectedDotPainter( - Theme.of(context), - viewModel.dataSeries[barIndex].control, - "selectedPoint", - barData.color, - barData.gradient, - percent); - var dotPainter = parseChartSelectedDotPainter( - Theme.of(context), - viewModel.dataSeries[barIndex].dataPoints[index] - .control, - "selectedPoint", - barData.color, - barData.gradient, - percent); - return dotPainter ?? - allDotsPainter ?? - getDefaultSelectedPainter( - barData.color, barData.gradient, percent); - }, - ), - ); - }).toList(); - }, - touchTooltipData: LineTouchTooltipData( - getTooltipColor: (LineBarSpot spot) => widget.control - .attrColor("tooltipBgColor", context, - const Color.fromRGBO(96, 125, 139, 1))!, - tooltipRoundedRadius: - widget.control.attrDouble("tooltipRoundedRadius", 4)!, - tooltipMargin: - widget.control.attrDouble("tooltipMargin", 16)!, - tooltipPadding: parseEdgeInsets( - widget.control, - "tooltipPadding", - const EdgeInsets.symmetric( - horizontal: 16, vertical: 8))!, - maxContentWidth: widget.control - .attrDouble("tooltipMaxContentWidth", 120)!, - rotateAngle: - widget.control.attrDouble("tooltipRotateAngle", 0.0)!, - tooltipHorizontalOffset: widget.control - .attrDouble("tooltipHorizontalOffset", 0)!, - tooltipBorder: parseBorderSide(Theme.of(context), - widget.control, "tooltipBorderSide") ?? - BorderSide.none, - fitInsideHorizontally: widget.control - .attrBool("tooltipFitInsideHorizontally", false)!, - fitInsideVertically: widget.control - .attrBool("tooltipFitInsideVertically", false)!, - showOnTopOfTheChartBoxArea: widget.control - .attrBool("tooltipShowOnTopOfChartBoxArea", false)!, - getTooltipItems: (touchedSpots) { - return touchedSpots.map((spot) { - var dp = viewModel.dataSeries[spot.barIndex] - .dataPoints[spot.spotIndex]; - var tooltip = dp.tooltip ?? dp.y.toString(); - var tooltipStyle = parseTextStyle( - Theme.of(context), dp.control, "tooltipStyle"); - tooltipStyle ??= const TextStyle(); - if (tooltipStyle.color == null) { - tooltipStyle = tooltipStyle.copyWith( - color: spot.bar.gradient?.colors.first ?? - spot.bar.color ?? - Colors.blueGrey); - } - TextAlign? tooltipAlign = parseTextAlign( - dp.control.attrString("tooltipAlign"), - TextAlign.center)!; - return dp.control.attrBool("showTooltip", true)! - ? LineTooltipItem(tooltip, tooltipStyle, - textAlign: tooltipAlign) - : null; - }).toList(); - }, - ), - touchCallback: widget.control.attrBool("onChartEvent", false)! - ? (evt, resp) { - var eventData = LineChartEventData( - eventType: evt.runtimeType - .toString() - .substring(2), // remove "Fl" - barSpots: - resp != null && resp.lineBarSpots != null - ? resp.lineBarSpots! - .map((bs) => LineChartEventDataSpot( - barIndex: bs.barIndex, - spotIndex: bs.spotIndex)) - .toList() - : []); - if (eventData != _eventData) { - _eventData = eventData; - debugPrint( - "LineChart ${widget.control.id} ${eventData.eventType}"); - widget.backend.triggerControlEvent( - widget.control.id, - "chart_event", - json.encode(eventData)); - } - } - : null, - )), - duration: animate != null - ? animate.duration - : const Duration(milliseconds: 150), // Optional - curve: animate != null ? animate.curve : Curves.linear, - ); - - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return (constraints.maxHeight == double.infinity) - ? ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 300), - child: chart, - ) - : chart; - }); - }); - - return constrainedControl(context, result, widget.parent, widget.control); - } - - LineChartBarData getBarData(ThemeData theme, Control parent, - bool interactiveChart, LineChartDataViewModel dataViewModel) { - Color? aboveLineBgcolor = - dataViewModel.control.attrColor("aboveLineBgcolor", context); - Gradient? aboveLineGradient = - parseGradient(theme, dataViewModel.control, "aboveLineGradient"); - Color? belowLineBgcolor = - dataViewModel.control.attrColor("belowLineBgcolor", context); - Gradient? belowLineGradient = - parseGradient(theme, dataViewModel.control, "belowLineGradient"); - var dashPattern = dataViewModel.control.attrString("dashPattern"); - var shadow = - parseBoxShadow(Theme.of(context), dataViewModel.control, "shadow", [])!; - Color barColor = - dataViewModel.control.attrColor("color", context) ?? Colors.cyan; - Gradient? barGradient = - parseGradient(theme, dataViewModel.control, "gradient"); - FlLine? aboveLine = - parseFlLine(Theme.of(context), dataViewModel.control, "aboveLine"); - FlLine? belowLine = - parseFlLine(Theme.of(context), dataViewModel.control, "belowLine"); - double? aboveLineCutoffY = - dataViewModel.control.attrDouble("aboveLineCutoffY"); - double? belowLineCutoffY = - dataViewModel.control.attrDouble("belowLineCutoffY"); - - Map spots = { - for (var e in dataViewModel.dataPoints) FlSpot(e.x, e.y): e - }; - return LineChartBarData( - preventCurveOverShooting: - dataViewModel.control.attrBool("preventCurveOverShooting", false)!, - preventCurveOvershootingThreshold: dataViewModel.control - .attrDouble("preventCurveOverShootingThreshold", 10.0)!, - spots: dataViewModel.dataPoints.map((p) => FlSpot(p.x, p.y)).toList(), - showingIndicators: dataViewModel.dataPoints - .asMap() - .entries - .where((e) => - !interactiveChart && - e.value.control.attrBool("selected", false)!) - .map((e) => e.key) - .toList(), - isCurved: dataViewModel.control.attrBool("curved", false)!, - isStrokeCapRound: - dataViewModel.control.attrBool("strokeCapRound", false)!, - barWidth: dataViewModel.control.attrDouble("strokeWidth") ?? 2.0, - dashArray: dashPattern != null - ? (json.decode(dashPattern) as List) - .map((e) => parseInt(e)) - .nonNulls - .toList() - : null, - shadow: shadow.isNotEmpty - ? shadow[0] - : const Shadow(color: Colors.transparent), - dotData: FlDotData( - show: true, - getDotPainter: (spot, percent, barData, index) { - var allDotsPainter = parseChartDotPainter( - theme, - dataViewModel.control, - "point", - barColor, - barGradient, - percent); - var dotPainter = parseChartDotPainter( - theme, - dataViewModel.dataPoints[index].control, - "point", - barColor, - barGradient, - percent); - return dotPainter ?? allDotsPainter ?? getInvisiblePainter(); - }), - aboveBarData: aboveLineBgcolor != null || - aboveLineGradient != null || - aboveLine != null - ? BarAreaData( - show: true, - color: aboveLineBgcolor, - gradient: aboveLineGradient, - applyCutOffY: aboveLineCutoffY != null, - cutOffY: aboveLineCutoffY ?? 0, - spotsLine: BarAreaSpotsLine( - show: aboveLine != null, - flLineStyle: aboveLine ?? const FlLine(), - checkToShowSpotLine: (spot) => - spots[spot]!.control.attrBool("showAboveLine", true)!, - )) - : null, - belowBarData: belowLineBgcolor != null || - belowLineGradient != null || - belowLine != null - ? BarAreaData( - show: true, - color: belowLineBgcolor, - gradient: belowLineGradient, - applyCutOffY: belowLineCutoffY != null, - cutOffY: belowLineCutoffY ?? 0, - spotsLine: BarAreaSpotsLine( - show: belowLine != null, - flLineStyle: belowLine ?? const FlLine(), - checkToShowSpotLine: (spot) => - spots[spot]!.control.attrBool("showBelowLine", true)!, - )) - : null, - color: barColor, - gradient: barGradient); - } - - AxisTitles getAxisTitles( - Control parent, ChartAxisViewModel? axisViewModel, bool disabled) { - if (axisViewModel == null) { - return const AxisTitles(sideTitles: SideTitles(showTitles: false)); - } - - return AxisTitles( - axisNameWidget: axisViewModel.title != null - ? createControl(parent, axisViewModel.title!.id, disabled) - : null, - axisNameSize: axisViewModel.control.attrDouble("titleSize") ?? 16, - sideTitles: SideTitles( - showTitles: axisViewModel.control.attrBool("showLabels", true)!, - reservedSize: axisViewModel.control.attrDouble("labelsSize") ?? 22, - interval: axisViewModel.control.attrDouble("labelsInterval"), - getTitlesWidget: axisViewModel.labels.isEmpty - ? defaultGetTitle - : (value, meta) { - return axisViewModel.labels.containsKey(value) - ? createControl( - parent, axisViewModel.labels[value]!.id, disabled) - : const SizedBox.shrink(); - }, - )); - } -} diff --git a/packages/flet/lib/src/controls/list_tile.dart b/packages/flet/lib/src/controls/list_tile.dart index 1153fff0a..491c2b59e 100644 --- a/packages/flet/lib/src/controls/list_tile.dart +++ b/packages/flet/lib/src/controls/list_tile.dart @@ -1,17 +1,18 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/borders.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; import '../utils/launch_url.dart'; +import '../utils/misc.dart'; import '../utils/mouse.dart'; -import '../utils/others.dart'; +import '../utils/numbers.dart'; import '../utils/text.dart'; import '../utils/theme.dart'; -import 'create_control.dart'; -import 'cupertino_list_tile.dart'; -import 'flet_store_mixin.dart'; +import '../widgets/flet_store_mixin.dart'; +import 'base_controls.dart'; class ListTileClicks extends InheritedWidget { const ListTileClicks({ @@ -31,141 +32,95 @@ class ListTileClicks extends InheritedWidget { } class ListTileControl extends StatelessWidget with FletStoreMixin { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; + final ListTileClickNotifier _clickNotifier = ListTileClickNotifier(); - ListTileControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + ListTileControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("ListTile build: ${control.id}"); - return withPagePlatform((context, platform) { - bool? adaptive = control.isAdaptive ?? parentAdaptive; - if (adaptive == true && - (platform == TargetPlatform.iOS || - platform == TargetPlatform.macOS)) { - return CupertinoListTileControl( - control: control, - parent: parent, - parentDisabled: parentDisabled, - parentAdaptive: adaptive, - children: children, - backend: backend); - } - - var leadingCtrls = - children.where((c) => c.name == "leading" && c.isVisible); - var titleCtrls = children.where((c) => c.name == "title" && c.isVisible); - var subtitleCtrls = - children.where((c) => c.name == "subtitle" && c.isVisible); - var trailingCtrls = - children.where((c) => c.name == "trailing" && c.isVisible); - - bool onclick = control.attrBool("onclick", false)!; - bool toggleInputs = control.attrBool("toggleInputs", false)!; - bool onLongPressDefined = control.attrBool("onLongPress", false)!; - String url = control.attrString("url", "")!; - String? urlTarget = control.attrString("urlTarget"); - bool disabled = control.isDisabled || parentDisabled; - - Function()? onPressed = - (onclick || toggleInputs || url != "") && !disabled - ? () { - debugPrint("ListTile ${control.id} clicked!"); - if (toggleInputs) { - _clickNotifier.onClick(); - } - if (url != "") { - openWebBrowser(url, webWindowName: urlTarget); - } - if (onclick) { - backend.triggerControlEvent(control.id, "click"); - } + var leading = control.buildIconOrWidget("leading"); + var title = control.buildTextOrWidget("title"); + var subtitle = control.buildTextOrWidget("subtitle"); + var trailing = control.buildIconOrWidget("trailing"); + var onClick = control.getBool("on_click", false)!; + var toggleInputs = control.getBool("toggle_inputs", false)!; + var url = control.getString("url"); + var urlTarget = control.getString("url_target"); + + Function()? onPressed = + (onClick || toggleInputs || url != "") && !control.disabled + ? () { + if (toggleInputs) { + _clickNotifier.onClick(); + } + if (url != null) { + openWebBrowser(url, webWindowName: urlTarget); + } + if (onClick) { + control.triggerEvent("click"); } - : null; - - Function()? onLongPress = onLongPressDefined && !disabled - ? () { - debugPrint("Button ${control.id} clicked!"); - backend.triggerControlEvent(control.id, "long_press"); - } - : null; - - Widget tile = ListTile( - autofocus: control.attrBool("autofocus", false)!, - contentPadding: parseEdgeInsets(control, "contentPadding"), - isThreeLine: control.attrBool("isThreeLine", false)!, - selected: control.attrBool("selected", false)!, - dense: control.attrBool("dense", false)!, - onTap: onPressed, - onLongPress: onLongPress, - enabled: !disabled, - horizontalTitleGap: control.attrDouble("horizontalSpacing"), - enableFeedback: control.attrBool("enableFeedback"), - minLeadingWidth: control.attrDouble("minLeadingWidth"), - minVerticalPadding: control.attrDouble("minVerticalPadding"), - minTileHeight: control.attrDouble("minHeight"), - selectedTileColor: control.attrColor("selectedTileColor", context), - selectedColor: control.attrColor("selectedColor", context), - focusColor: control.attrColor("focusColor", context), - tileColor: control.attrColor("bgcolor", context), - splashColor: control.attrColor("bgcolorActivated", context), - hoverColor: control.attrColor("hoverColor", context), - iconColor: control.attrColor("iconColor", context), - textColor: control.attrColor("textColor", context), - mouseCursor: parseMouseCursor(control.attrString("mouseCursor")), - visualDensity: parseVisualDensity(control.attrString("visualDensity")), - shape: parseOutlinedBorder(control, "shape"), - titleTextStyle: - parseTextStyle(Theme.of(context), control, "titleTextStyle"), - leadingAndTrailingTextStyle: parseTextStyle( - Theme.of(context), control, "leadingAndTrailingTextStyle"), - subtitleTextStyle: - parseTextStyle(Theme.of(context), control, "subtitleTextStyle"), - titleAlignment: - parseListTileTitleAlignment(control.attrString("titleAlignment")), - style: parseListTileStyle(control.attrString("style")), - onFocusChange: (bool hasFocus) { - backend.triggerControlEvent(control.id, hasFocus ? "focus" : "blur"); - }, - leading: leadingCtrls.isNotEmpty - ? createControl(control, leadingCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null, - title: titleCtrls.isNotEmpty - ? createControl(control, titleCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null, - subtitle: subtitleCtrls.isNotEmpty - ? createControl(control, subtitleCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null, - trailing: trailingCtrls.isNotEmpty - ? createControl(control, trailingCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null, - ); - - if (toggleInputs) { - tile = ListTileClicks(notifier: _clickNotifier, child: tile); - } - - tile = Material(color: Colors.transparent, child: tile); - - return constrainedControl(context, tile, parent, control); - }); + } + : null; + + Function()? onLongPress = + control.getBool("on_long_press", false)! && !control.disabled + ? () { + control.triggerEvent("long_press"); + } + : null; + + Widget tile = ListTile( + autofocus: control.getBool("autofocus", false)!, + contentPadding: control.getPadding("content_padding"), + isThreeLine: control.getBool("is_three_line", false)!, + selected: control.getBool("selected", false)!, + dense: control.getBool("dense", false)!, + onTap: onPressed, + onLongPress: onLongPress, + enabled: !control.disabled, + horizontalTitleGap: control.getDouble("horizontal_spacing"), + enableFeedback: control.getBool("enable_feedback"), + minLeadingWidth: control.getDouble("min_leading_width"), + minVerticalPadding: control.getDouble("min_vertical_padding"), + minTileHeight: control.getDouble("min_height"), + selectedTileColor: control.getColor("selected_tile_color", context), + selectedColor: control.getColor("selected_color", context), + focusColor: control.getColor("focus_color", context), + tileColor: control.getColor("bgcolor", context), + splashColor: control.getColor("bgcolor_activated", context), + hoverColor: control.getColor("hover_color", context), + iconColor: control.getColor("icon_color", context), + textColor: control.getColor("text_color", context), + mouseCursor: control.getMouseCursor("mouse_cursor"), + visualDensity: control.getVisualDensity("visual_density"), + shape: control.getShape("shape", Theme.of(context)), + titleTextStyle: + control.getTextStyle("title_text_style", Theme.of(context)), + leadingAndTrailingTextStyle: control.getTextStyle( + "leading_and_trailing_text_style", Theme.of(context)), + subtitleTextStyle: + control.getTextStyle("subtitle_text_style", Theme.of(context)), + titleAlignment: control.getListTileTitleAlignment("title_alignment"), + style: control.getListTileStyle("style"), + onFocusChange: (bool hasFocus) { + control.triggerEvent(hasFocus ? "focus" : "blur"); + }, + leading: leading, + title: title, + subtitle: subtitle, + trailing: trailing, + ); + + if (toggleInputs) { + tile = ListTileClicks(notifier: _clickNotifier, child: tile); + } + + tile = Material(type: MaterialType.transparency, child: tile); + + return ConstrainedControl(control: control, child: tile); } } diff --git a/packages/flet/lib/src/controls/list_view.dart b/packages/flet/lib/src/controls/list_view.dart index 484264f14..16ded5b8b 100644 --- a/packages/flet/lib/src/controls/list_view.dart +++ b/packages/flet/lib/src/controls/list_view.dart @@ -1,29 +1,19 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/edge_insets.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; import 'scroll_notification_control.dart'; import 'scrollable_control.dart'; class ListViewControl extends StatefulWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final List children; - final bool? parentAdaptive; - final FletControlBackend backend; - const ListViewControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + ListViewControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _ListViewControlState(); @@ -47,32 +37,25 @@ class _ListViewControlState extends State { @override Widget build(BuildContext context) { debugPrint("ListViewControl build: ${widget.control.id}"); - - bool disabled = widget.control.isDisabled || widget.parentDisabled; - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - - var horizontal = widget.control.attrBool("horizontal", false)!; - var spacing = widget.control.attrDouble("spacing", 0)!; - var dividerThickness = widget.control.attrDouble("dividerThickness", 0)!; - var itemExtent = widget.control.attrDouble("itemExtent"); - var cacheExtent = widget.control.attrDouble("cacheExtent"); - var semanticChildCount = widget.control.attrInt("semanticChildCount"); - var firstItemPrototype = - widget.control.attrBool("firstItemPrototype", false)!; - var padding = parseEdgeInsets(widget.control, "padding"); - var reverse = widget.control.attrBool("reverse", false)!; + var horizontal = widget.control.getBool("horizontal", false)!; + var spacing = widget.control.getDouble("spacing", 0)!; + var dividerThickness = widget.control.getDouble("divider_thickness", 0)!; + var itemExtent = widget.control.getDouble("item_extent"); + var cacheExtent = widget.control.getDouble("cache_extent"); + var semanticChildCount = widget.control.getInt("semantic_child_count"); + var padding = widget.control.getPadding("padding"); + var reverse = widget.control.getBool("reverse", false)!; var clipBehavior = - parseClip(widget.control.attrString("clipBehavior"), Clip.hardEdge)!; - - List ctrls = widget.children.where((c) => c.isVisible).toList(); + parseClip(widget.control.getString("clip_behavior"), Clip.hardEdge)!; var scrollDirection = horizontal ? Axis.horizontal : Axis.vertical; var buildControlsOnDemand = - widget.control.attrBool("buildControlsOnDemand", true)!; - var prototypeItem = firstItemPrototype && widget.children.isNotEmpty - ? createControl(widget.control, ctrls[0].id, disabled, - parentAdaptive: adaptive) + widget.control.getBool("build_controls_on_demand", true)!; + var firstItemPrototype = + widget.control.getBool("first_item_prototype", false)!; + var prototypeItem = firstItemPrototype + ? widget.control.buildWidget("prototype_item") : null; + List controls = widget.control.buildWidgets("controls"); Widget listView = LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { @@ -94,10 +77,7 @@ class _ListViewControlState extends State { semanticChildCount: semanticChildCount, itemExtent: itemExtent, prototypeItem: prototypeItem, - children: ctrls - .map((c) => createControl(widget.control, c.id, disabled, - parentAdaptive: adaptive)) - .toList(), + children: controls, ) : spacing > 0 ? ListView.separated( @@ -108,11 +88,9 @@ class _ListViewControlState extends State { scrollDirection: scrollDirection, shrinkWrap: shrinkWrap, padding: padding, - itemCount: widget.children.length, + itemCount: controls.length, itemBuilder: (context, index) { - return createControl( - widget.control, ctrls[index].id, disabled, - parentAdaptive: adaptive); + return controls[index]; }, separatorBuilder: (context, index) { return horizontal @@ -135,12 +113,10 @@ class _ListViewControlState extends State { scrollDirection: scrollDirection, shrinkWrap: shrinkWrap, padding: padding, - itemCount: widget.children.length, + itemCount: controls.length, itemExtent: itemExtent, itemBuilder: (context, index) { - return createControl( - widget.control, ctrls[index].id, disabled, - parentAdaptive: adaptive); + return controls[index]; }, prototypeItem: prototypeItem, ); @@ -149,19 +125,17 @@ class _ListViewControlState extends State { control: widget.control, scrollDirection: horizontal ? Axis.horizontal : Axis.vertical, scrollController: _controller, - backend: widget.backend, - parentAdaptive: adaptive, child: child); - if (widget.control.attrBool("onScroll", false)!) { - child = ScrollNotificationControl( - control: widget.control, backend: widget.backend, child: child); + if (widget.control.getBool("on_scroll", false)!) { + child = + ScrollNotificationControl(control: widget.control, child: child); } return child; }, ); - return constrainedControl(context, listView, widget.parent, widget.control); + return ConstrainedControl(control: widget.control, child: listView); } } diff --git a/packages/flet/lib/src/controls/markdown.dart b/packages/flet/lib/src/controls/markdown.dart index fd5007f76..2a0e25f88 100644 --- a/packages/flet/lib/src/controls/markdown.dart +++ b/packages/flet/lib/src/controls/markdown.dart @@ -1,137 +1,110 @@ -import 'dart:convert'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:markdown/markdown.dart' as md; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/box.dart'; import '../utils/images.dart'; import '../utils/launch_url.dart'; import '../utils/markdown.dart'; +import '../utils/numbers.dart'; import '../utils/uri.dart'; -import 'create_control.dart'; -import 'error.dart'; -import 'flet_store_mixin.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; import 'highlight_view.dart'; -class MarkdownControl extends StatelessWidget with FletStoreMixin { - final Control? parent; - final List children; +class MarkdownControl extends StatelessWidget { final Control control; - final bool parentDisabled; - final FletControlBackend backend; static const String svgTag = " xmlns=\"http://www.w3.org/2000/svg\""; - const MarkdownControl( - {super.key, - required this.parent, - required this.children, - required this.control, - required this.parentDisabled, - required this.backend}); + const MarkdownControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("Markdown build: ${control.id}"); - bool disabled = control.isDisabled || parentDisabled; - - var value = control.attrString("value", "")!; - md.ExtensionSet extensionSet = parseMarkdownExtensionSet( - control.attrString("extensionSet"), md.ExtensionSet.none)!; - - var autoFollowLinks = control.attrBool("autoFollowLinks", false)!; - var autoFollowLinksTarget = control.attrString("autoFollowLinksTarget"); - - return withPageArgs((context, pageArgs) { - bool selectable = control.attrBool("selectable", false)!; - var codeStyleSheet = parseMarkdownStyleSheet( - control, "codeStyleSheet", Theme.of(context), pageArgs) ?? - MarkdownStyleSheet.fromTheme(Theme.of(context)).copyWith( - code: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(fontFamily: "monospace")); - var mdStyleSheet = parseMarkdownStyleSheet( - control, "mdStyleSheet", Theme.of(context), pageArgs); - var codeTheme = - parseMarkdownCodeTheme(control, "codeTheme", Theme.of(context)); - Widget markdown = MarkdownBody( - data: value, - selectable: selectable, - imageDirectory: pageArgs.assetsDir != "" - ? pageArgs.assetsDir - : getBaseUri(pageArgs.pageUri!).toString(), - extensionSet: extensionSet, - builders: { - 'code': CodeElementBuilder(codeTheme, codeStyleSheet, selectable), - }, - styleSheet: mdStyleSheet, - imageBuilder: (Uri uri, String? title, String? alt) { - String s = uri.toString(); - var srcBase64 = isBase64ImageString(s) ? s : null; - var src = isUrlOrPath(s) ? s : null; - if (src == null && srcBase64 == null) { - return ErrorControl("Invalid image URI: $s"); - } - var errorContentCtrls = - children.where((c) => c.name == "error" && c.isVisible); - - var errorContent = errorContentCtrls.isNotEmpty - ? createControl(control, errorContentCtrls.first.id, disabled) - : null; - - return buildImage( + + var value = control.getString("value", "")!; + md.ExtensionSet extensionSet = + control.getMarkdownExtensionSet("extension_set", md.ExtensionSet.none)!; + + var autoFollowLinks = control.getBool("auto_follow_links", false)!; + var autoFollowLinksTarget = control.getString("auto_follow_links_target"); + + bool selectable = control.getBool("selectable", false)!; + var codeStyleSheet = + control.getMarkdownStyleSheet("code_style_sheet", context) ?? + MarkdownStyleSheet.fromTheme(Theme.of(context)).copyWith( + code: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(fontFamily: "monospace")); + var mdStyleSheet = control.getMarkdownStyleSheet("md_style_sheet", context); + var codeTheme = + control.getMarkdownCodeTheme("code_theme", Theme.of(context)); + Widget markdown = MarkdownBody( + data: value, + selectable: selectable, + imageDirectory: control.backend.assetsDir != "" + ? control.backend.assetsDir + : getBaseUri(control.backend.pageUri).toString(), + extensionSet: extensionSet, + builders: { + 'code': CodeElementBuilder(codeTheme, codeStyleSheet, selectable), + }, + styleSheet: mdStyleSheet, + imageBuilder: (Uri uri, String? title, String? alt) { + String s = uri.toString(); + var srcBase64 = isBase64ImageString(s) ? s : null; + var src = isUrlOrPath(s) ? s : null; + if (src == null && srcBase64 == null) { + return ErrorControl("Invalid image URI: $s"); + } + + return buildImage( context: context, control: control, src: src, srcBase64: srcBase64, + srcBytes: Uint8List(0), semanticsLabel: alt, - disabled: disabled, - pageArgs: pageArgs, - errorCtrl: errorContent, - ); - }, - shrinkWrap: control.attrBool("shrinkWrap", true)!, - fitContent: control.attrBool("fitContent", true)!, - softLineBreak: control.attrBool("softLineBreak", false)!, - onSelectionChanged: (String? text, TextSelection selection, - SelectionChangedCause? cause) { - debugPrint("Markdown ${control.id} selection changed"); - backend.triggerControlEvent( - control.id, - "selection_change", - jsonEncode({ - "text": text ?? "", - "start": selection.start, - "end": selection.end, - "base_offset": selection.baseOffset, - "extent_offset": selection.extentOffset, - "affinity": selection.affinity.name, - "directional": selection.isDirectional, - "collapsed": selection.isCollapsed, - "valid": selection.isValid, - "normalized": selection.isNormalized, - "cause": cause?.name ?? "unknown", - })); - }, - onTapText: () { - debugPrint("Markdown ${control.id} text tapped"); - backend.triggerControlEvent(control.id, "tap_text"); - }, - onTapLink: (String text, String? href, String title) { - debugPrint("Markdown ${control.id} link tapped clicked"); - if (autoFollowLinks && href != null) { - openWebBrowser(href, webWindowName: autoFollowLinksTarget); - } - backend.triggerControlEvent( - control.id, "tap_link", href?.toString()); + disabled: control.disabled, + errorCtrl: control.buildWidget("img_error_content")); + }, + shrinkWrap: control.getBool("shrink_wrap", true)!, + fitContent: control.getBool("fit_content", true)!, + softLineBreak: control.getBool("soft_line_break", false)!, + onSelectionChanged: (String? text, TextSelection selection, + SelectionChangedCause? cause) { + control.triggerEvent("selection_change", { + "text": text ?? "", + "cause": cause?.name ?? "unknown", + "selection": { + "start": selection.start, + "end": selection.end, + "selection": text ?? "", + "base_offset": selection.baseOffset, + "extent_offset": selection.extentOffset, + "affinity": selection.affinity.name, + "directional": selection.isDirectional, + "collapsed": selection.isCollapsed, + "valid": selection.isValid, + "normalized": selection.isNormalized, + }, }); - - return constrainedControl(context, markdown, parent, control); - }); + }, + onTapText: () => control.triggerEvent("tap_text"), + onTapLink: (String text, String? href, String title) { + if (autoFollowLinks && href != null) { + openWebBrowser(href, webWindowName: autoFollowLinksTarget); + } + control.triggerEvent("tap_link", href); + }); + + return ConstrainedControl(control: control, child: markdown); } } diff --git a/packages/flet/lib/src/controls/menu_bar.dart b/packages/flet/lib/src/controls/menu_bar.dart index 3b15b3f6b..d5c4cffc3 100644 --- a/packages/flet/lib/src/controls/menu_bar.dart +++ b/packages/flet/lib/src/controls/menu_bar.dart @@ -1,25 +1,11 @@ +import 'package:flet/flet.dart'; import 'package:flutter/material.dart'; -import '../models/control.dart'; -import '../utils/menu.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'error.dart'; - class MenuBarControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - const MenuBarControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive}); + MenuBarControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _MenuBarControlState(); @@ -30,26 +16,17 @@ class _MenuBarControlState extends State { Widget build(BuildContext context) { debugPrint("MenuBar build: ${widget.control.id}"); - var ctrls = widget.children.where((c) => c.isVisible).toList(); - if (ctrls.isEmpty) { + var controls = widget.control.buildWidgets("controls"); + if (controls.isEmpty) { return const ErrorControl( "MenuBar must have at minimum one visible child control"); } + final menuBar = MenuBar( + style: widget.control.getMenuStyle("style", Theme.of(context)), + clipBehavior: + widget.control.getClipBehavior("clip_behavior", Clip.none)!, + children: controls); - return constrainedControl( - context, - MenuBar( - style: parseMenuStyle(Theme.of(context), widget.control, "style"), - clipBehavior: - parseClip(widget.control.attrString("clipBehavior"), Clip.none)!, - children: ctrls - .map((c) => createControl(widget.control, c.id, - widget.control.isDisabled || widget.parentDisabled, - parentAdaptive: - widget.control.isAdaptive ?? widget.parentAdaptive)) - .toList(), - ), - widget.parent, - widget.control); + return ConstrainedControl(control: widget.control, child: menuBar); } } diff --git a/packages/flet/lib/src/controls/menu_item_button.dart b/packages/flet/lib/src/controls/menu_item_button.dart index 79c715922..a062c3d72 100644 --- a/packages/flet/lib/src/controls/menu_item_button.dart +++ b/packages/flet/lib/src/controls/menu_item_button.dart @@ -1,27 +1,17 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/buttons.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; class MenuItemButtonControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const MenuItemButtonControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + MenuItemButtonControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _MenuItemButtonControlState(); @@ -46,24 +36,15 @@ class _MenuItemButtonControlState extends State { } void _onFocusChange() { - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); } @override Widget build(BuildContext context) { debugPrint("MenuItemButton build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - var content = - widget.children.where((c) => c.name == "content" && c.isVisible); - var leading = - widget.children.where((c) => c.name == "leading" && c.isVisible); - var trailing = - widget.children.where((c) => c.name == "trailing" && c.isVisible); var theme = Theme.of(context); - var style = parseButtonStyle(Theme.of(context), widget.control, "style", + var style = widget.control.getButtonStyle("style", Theme.of(context), defaultForegroundColor: theme.colorScheme.primary, defaultBackgroundColor: Colors.transparent, defaultOverlayColor: Colors.transparent, @@ -76,53 +57,35 @@ class _MenuItemButtonControlState extends State { ? const StadiumBorder() : RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))); - bool onClick = widget.control.attrBool("onClick", false)!; - bool onHover = widget.control.attrBool("onHover", false)!; - - var adaptive = widget.control.isAdaptive ?? widget.parentAdaptive; + bool onClick = widget.control.getBool("on_click", false)!; + bool onHover = widget.control.getBool("on_hover", false)!; var menuItem = MenuItemButton( focusNode: _focusNode, - clipBehavior: - parseClip(widget.control.attrString("clipBehavior"), Clip.none)!, + clipBehavior: widget.control.getClipBehavior("clip_behavior", Clip.none)!, style: style, - closeOnActivate: widget.control.attrBool("closeOnClick", true)!, - requestFocusOnHover: widget.control.attrBool("focusOnHover", true)!, - semanticsLabel: widget.control.attrString("semanticsLabel"), - autofocus: widget.control.attrBool("autofocus", false)!, - overflowAxis: parseAxis( - widget.control.attrString("overflowAxis"), Axis.horizontal)!, - onHover: onHover && !disabled - ? (bool value) { - widget.backend - .triggerControlEvent(widget.control.id, "hover", "$value"); - } - : null, - onPressed: onClick && !disabled - ? () { - widget.backend.triggerControlEvent(widget.control.id, "click"); - } - : null, - leadingIcon: leading.isNotEmpty - ? createControl(widget.control, leading.first.id, disabled, - parentAdaptive: adaptive) - : null, - trailingIcon: trailing.isNotEmpty - ? createControl(widget.control, trailing.first.id, disabled, - parentAdaptive: adaptive) + closeOnActivate: widget.control.getBool("close_on_click", true)!, + requestFocusOnHover: widget.control.getBool("focus_on_hover", true)!, + semanticsLabel: widget.control.getString("semantics_label"), + autofocus: widget.control.getBool("autofocus", false)!, + overflowAxis: widget.control.getAxis("overflow_axis", Axis.horizontal)!, + onHover: onHover && !widget.control.disabled + ? (bool value) => widget.control.triggerEvent("hover", value) : null, - child: content.isNotEmpty - ? createControl(widget.control, content.first.id, disabled, - parentAdaptive: adaptive) + onPressed: onClick && !widget.control.disabled + ? () => widget.control.triggerEvent("click") : null, + leadingIcon: widget.control.buildWidget("leading"), + trailingIcon: widget.control.buildWidget("trailing_icon"), + child: widget.control.buildTextOrWidget("content"), ); - var focusValue = widget.control.attrString("focus"); + var focusValue = widget.control.getString("focus"); if (focusValue != null && focusValue != _lastFocusValue) { _lastFocusValue = focusValue; _focusNode.requestFocus(); } - return constrainedControl(context, menuItem, widget.parent, widget.control); + return ConstrainedControl(control: widget.control, child: menuItem); } } diff --git a/packages/flet/lib/src/controls/merge_semantics.dart b/packages/flet/lib/src/controls/merge_semantics.dart index 52ec645c1..c9aa6f569 100644 --- a/packages/flet/lib/src/controls/merge_semantics.dart +++ b/packages/flet/lib/src/controls/merge_semantics.dart @@ -1,37 +1,20 @@ import 'package:flutter/material.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import 'create_control.dart'; +import 'base_controls.dart'; class MergeSemanticsControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - const MergeSemanticsControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive}); + const MergeSemanticsControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("MergeSemantics build: ${control.id}"); - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - bool disabled = control.isDisabled || parentDisabled; - - MergeSemantics mergeSemantics = MergeSemantics( - child: contentCtrls.isNotEmpty - ? createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: parentAdaptive) - : null); - - return constrainedControl(context, mergeSemantics, parent, control); + return ConstrainedControl( + control: control, + child: MergeSemantics(child: control.buildWidget("content"))); } } diff --git a/packages/flet/lib/src/controls/navigation_bar.dart b/packages/flet/lib/src/controls/navigation_bar.dart index cd848c857..96d782c66 100644 --- a/packages/flet/lib/src/controls/navigation_bar.dart +++ b/packages/flet/lib/src/controls/navigation_bar.dart @@ -1,35 +1,22 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/borders.dart'; import '../utils/colors.dart'; import '../utils/edge_insets.dart'; -import '../utils/icons.dart'; -import '../utils/others.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; import '../utils/time.dart'; -import 'create_control.dart'; +import '../widgets/flet_store_mixin.dart'; +import 'base_controls.dart'; import 'cupertino_navigation_bar.dart'; -import 'flet_store_mixin.dart'; class NavigationBarControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const NavigationBarControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + NavigationBarControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _NavigationBarControlState(); @@ -41,11 +28,9 @@ class _NavigationBarControlState extends State void _destinationChanged(int index) { _selectedIndex = index; - debugPrint("Selected index: $_selectedIndex"); - widget.backend.updateControlState( - widget.control.id, {"selectedIndex": _selectedIndex.toString()}); - widget.backend.triggerControlEvent( - widget.control.id, "change", _selectedIndex.toString()); + widget.control + .updateProperties({"selected_index": _selectedIndex}, notify: true); + widget.control.triggerEvent("change", _selectedIndex); } @override @@ -53,81 +38,39 @@ class _NavigationBarControlState extends State debugPrint("NavigationBarControl build: ${widget.control.id}"); return withPagePlatform((context, platform) { - bool? adaptive = widget.control.isAdaptive ?? widget.parentAdaptive; - if (adaptive == true && + if (widget.control.adaptive == true && (platform == TargetPlatform.iOS || platform == TargetPlatform.macOS)) { - return CupertinoNavigationBarControl( - control: widget.control, - children: widget.children, - parentDisabled: widget.parentDisabled, - parentAdaptive: adaptive, - backend: widget.backend); + return CupertinoNavigationBarControl(control: widget.control); } - bool disabled = widget.control.isDisabled || widget.parentDisabled; - var selectedIndex = widget.control.attrInt("selectedIndex", 0)!; + var selectedIndex = widget.control.getInt("selected_index", 0)!; if (_selectedIndex != selectedIndex) { _selectedIndex = selectedIndex; } - var navBar = withControls( - widget.children - .where((c) => c.isVisible && c.name == null) - .map((c) => c.id), (content, viewModel) { - return NavigationBar( - labelBehavior: parseNavigationDestinationLabelBehavior( - widget.control.attrString("labelBehavior")), - height: widget.control.attrDouble("height"), - animationDuration: - parseDuration(widget.control, "animationDuration"), - elevation: widget.control.attrDouble("elevation"), - labelPadding: parseEdgeInsets(widget.control, "labelPadding"), - shadowColor: widget.control.attrColor("shadowColor", context), - surfaceTintColor: - widget.control.attrColor("surfaceTintColor", context), - overlayColor: parseWidgetStateColor( - Theme.of(context), widget.control, "overlayColor"), - indicatorColor: widget.control.attrColor("indicatorColor", context), - indicatorShape: - parseOutlinedBorder(widget.control, "indicatorShape"), - backgroundColor: widget.control.attrColor("bgColor", context), - selectedIndex: _selectedIndex, - onDestinationSelected: disabled ? null : _destinationChanged, - destinations: viewModel.controlViews.map((destView) { - var label = destView.control.attrString("label", "")!; - var iconStr = parseIcon(destView.control.attrString("icon")); - var iconCtrls = destView.children - .where((c) => c.name == "icon" && c.isVisible); - var selectedIconStr = - parseIcon(destView.control.attrString("selectedIcon")); - var selectedIconCtrls = destView.children - .where((c) => c.name == "selected_icon" && c.isVisible); - var destinationDisabled = disabled || destView.control.isDisabled; - var destinationAdaptive = destView.control.isAdaptive ?? adaptive; - var destinationTooltip = destView.control.attrString("tooltip"); - return NavigationDestination( - enabled: !destinationDisabled, - tooltip: !destinationDisabled && destinationTooltip != null - ? jsonDecode(destinationTooltip) - : null, - icon: iconCtrls.isNotEmpty - ? createControl(destView.control, iconCtrls.first.id, - destinationDisabled, - parentAdaptive: destinationAdaptive) - : Icon(iconStr), - selectedIcon: selectedIconCtrls.isNotEmpty - ? createControl(destView.control, - selectedIconCtrls.first.id, destinationDisabled, - parentAdaptive: destinationAdaptive) - : selectedIconStr != null - ? Icon(selectedIconStr) - : null, - label: label); - }).toList()); - }); + var navBar = NavigationBar( + labelBehavior: widget.control + .getNavigationDestinationLabelBehavior("label_behavior"), + height: widget.control.getDouble("height"), + animationDuration: widget.control.getDuration("animation_duration"), + elevation: widget.control.getDouble("elevation"), + labelPadding: widget.control.getPadding("label_padding"), + shadowColor: widget.control.getColor("shadow_color", context), + surfaceTintColor: + widget.control.getColor("surface_tint_color", context), + overlayColor: widget.control + .getWidgetStateColor("overlay_color", Theme.of(context)), + indicatorColor: widget.control.getColor("indicator_color", context), + indicatorShape: + widget.control.getShape("indicator_shape", Theme.of(context)), + backgroundColor: widget.control.getColor("bgcolor", context), + selectedIndex: _selectedIndex, + onDestinationSelected: + widget.control.disabled ? null : _destinationChanged, + destinations: widget.control.buildWidgets("destinations")); - return constrainedControl(context, navBar, widget.parent, widget.control); + return ConstrainedControl(control: widget.control, child: navBar); }); } } diff --git a/packages/flet/lib/src/controls/navigation_bar_destination.dart b/packages/flet/lib/src/controls/navigation_bar_destination.dart new file mode 100644 index 000000000..7d2d20244 --- /dev/null +++ b/packages/flet/lib/src/controls/navigation_bar_destination.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +import '../extensions/control.dart'; +import '../models/control.dart'; +import '../utils/icons.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; + +class NavigationBarDestinationControl extends StatelessWidget { + final Control control; + + const NavigationBarDestinationControl({super.key, required this.control}); + + @override + Widget build(BuildContext context) { + debugPrint("NavigationBarDestination build: ${control.id}"); + + var icon = parseIcon(control.getString("icon")); + var selectedIcon = parseIcon(control.getString("selected_icon")); + var child = NavigationDestination( + enabled: !control.disabled, + tooltip: !control.disabled ? control.getString("tooltip") : null, + icon: control.buildWidget("icon") ?? Icon(icon), + selectedIcon: control.buildWidget("selected_icon") ?? + (selectedIcon != null ? Icon(selectedIcon) : null), + label: control.getString("label", "")!); + + return BaseControl(control: control, child: child); + } +} diff --git a/packages/flet/lib/src/controls/navigation_drawer.dart b/packages/flet/lib/src/controls/navigation_drawer.dart index 8a23c7c7c..54ad4c061 100644 --- a/packages/flet/lib/src/controls/navigation_drawer.dart +++ b/packages/flet/lib/src/controls/navigation_drawer.dart @@ -1,111 +1,104 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; import '../utils/borders.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; import '../utils/icons.dart'; -import 'create_control.dart'; -import 'flet_store_mixin.dart'; +import '../utils/numbers.dart'; +import '../widgets/scaffold_key_provider.dart'; +import 'base_controls.dart'; +import 'control_widget.dart'; class NavigationDrawerControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const NavigationDrawerControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + NavigationDrawerControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _NavigationDrawerControlState(); } -class _NavigationDrawerControlState extends State - with FletStoreMixin { +class _NavigationDrawerControlState extends State { int _selectedIndex = 0; void _destinationChanged(int index) { _selectedIndex = index; debugPrint("Selected index: $_selectedIndex"); - widget.backend.updateControlState( - widget.control.id, {"selectedindex": _selectedIndex.toString()}); - widget.backend.triggerControlEvent( - widget.control.id, "change", _selectedIndex.toString()); + widget.control + .updateProperties({"selected_index": _selectedIndex}, notify: true); + widget.control.triggerEvent("change", _selectedIndex); } @override Widget build(BuildContext context) { debugPrint("NavigationDrawerControl build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - var selectedIndex = widget.control.attrInt("selectedIndex", 0)!; + var selectedIndex = widget.control.getInt("selected_index", 0)!; + var endDrawer = widget.control.get("position") == "end"; if (_selectedIndex != selectedIndex) { _selectedIndex = selectedIndex; } - return withControls( - widget.children - .where((c) => c.isVisible && c.name == null) - .map((c) => c.id), (content, viewModel) { - List children = viewModel.controlViews.map((destView) { - if (destView.control.type == "navigationdrawerdestination") { - var iconStr = parseIcon(destView.control.attrString("icon")); - var iconCtrls = destView.children - .where((c) => c.name == "icon" && c.isVisible); - - var selectedIconStr = - parseIcon(destView.control.attrString("selectedIcon")); - var selectedIconCtrls = destView.children - .where((c) => c.name == "selected_icon" && c.isVisible); + var drawer = NavigationDrawer( + elevation: widget.control.getDouble("elevation"), + indicatorColor: widget.control.getColor("indicator_color", context), + indicatorShape: widget.control + .getOutlinedBorder("indicator_shape", Theme.of(context)), + backgroundColor: widget.control.getColor("bgcolor", context), + selectedIndex: _selectedIndex, + shadowColor: widget.control.getColor("shadow_color", context), + surfaceTintColor: widget.control.getColor("surface_tint_color", context), + tilePadding: parseEdgeInsets(widget.control.get("tile_padding"), + const EdgeInsets.symmetric(horizontal: 12.0))!, + onDestinationSelected: _destinationChanged, + children: widget.control.children("controls").map((dest) { + dest.notifyParent = true; + if (dest.type == "NavigationDrawerDestination") { + var icon = dest.get("icon"); + var selectedIcon = dest.get("selected_icon"); + return NavigationDrawerDestination( - enabled: !(disabled || destView.control.isDisabled), - backgroundColor: destView.control.attrColor("bgColor", context), - icon: iconCtrls.isNotEmpty - ? createControl( - destView.control, iconCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : Icon(iconStr), - label: Text(destView.control.attrString("label", "")!), - selectedIcon: selectedIconCtrls.isNotEmpty - ? createControl(destView.control, - selectedIconCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : selectedIconStr != null - ? Icon(selectedIconStr) + enabled: !dest.disabled, + backgroundColor: dest.getColor("bgcolor", context), + icon: icon is Control + ? ControlWidget( + control: icon, + ) + : Icon(parseIcon(icon)), + label: Text(dest.getString("label", "")!), + selectedIcon: selectedIcon is Control + ? ControlWidget( + control: selectedIcon, + ) + : selectedIcon is String + ? Icon(parseIcon(selectedIcon)) : null, ); } else { - return createControl(widget.control, destView.control.id, disabled, - parentAdaptive: widget.parentAdaptive); + return ControlWidget(control: dest); } - }).toList(); - - var drawer = NavigationDrawer( - elevation: widget.control.attrDouble("elevation"), - indicatorColor: widget.control.attrColor("indicatorColor", context), - indicatorShape: parseOutlinedBorder(widget.control, "indicatorShape"), - backgroundColor: widget.control.attrColor("bgColor", context), - selectedIndex: _selectedIndex, - shadowColor: widget.control.attrColor("shadowColor", context), - surfaceTintColor: widget.control.attrColor("surfaceTintColor", context), - tilePadding: parseEdgeInsets(widget.control, "tilePadding", - const EdgeInsets.symmetric(horizontal: 12.0))!, - onDestinationSelected: _destinationChanged, - children: children, - ); + }).toList(), + ); - return baseControl(context, drawer, widget.parent, widget.control); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (widget.control.getBool("open", false) == false) { + if (endDrawer && + ScaffoldKeyProvider.of(context)?.currentState?.isEndDrawerOpen == + true) { + ScaffoldKeyProvider.of(context)?.currentState?.closeEndDrawer(); + } else if (ScaffoldKeyProvider.of(context) + ?.currentState + ?.isDrawerOpen == + true) { + ScaffoldKeyProvider.of(context)?.currentState?.closeDrawer(); + } + } }); + + return BaseControl(control: widget.control, child: drawer); } } diff --git a/packages/flet/lib/src/controls/navigation_rail.dart b/packages/flet/lib/src/controls/navigation_rail.dart index 96e771a82..1580ee038 100644 --- a/packages/flet/lib/src/controls/navigation_rail.dart +++ b/packages/flet/lib/src/controls/navigation_rail.dart @@ -1,31 +1,23 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/borders.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; import '../utils/icons.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; import '../utils/text.dart'; -import 'create_control.dart'; -import 'error.dart'; -import 'flet_store_mixin.dart'; +import '../widgets/error.dart'; +import '../widgets/flet_store_mixin.dart'; +import 'base_controls.dart'; class NavigationRailControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const NavigationRailControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + NavigationRailControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _NavigationRailControlState(); @@ -37,125 +29,89 @@ class _NavigationRailControlState extends State void _destinationChanged(int index) { _selectedIndex = index; - debugPrint("NavigationRail selectedIndex: $_selectedIndex"); - widget.backend.updateControlState( - widget.control.id, {"selectedindex": _selectedIndex.toString()}); - widget.backend.triggerControlEvent( - widget.control.id, "change", _selectedIndex.toString()); + debugPrint("NavigationRail selected_index: $_selectedIndex"); + widget.control + .updateProperties({"selected_index": _selectedIndex}, notify: true); + widget.control.triggerEvent("change", _selectedIndex); } @override Widget build(BuildContext context) { debugPrint("NavigationRailControl build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - var selectedIndex = widget.control.attrInt("selectedIndex"); + bool disabled = widget.control.disabled; + var selectedIndex = widget.control.getInt("selected_index"); if (_selectedIndex != selectedIndex) { _selectedIndex = selectedIndex; } - NavigationRailLabelType? labelType = NavigationRailLabelType.values - .firstWhere( - (a) => - a.name.toLowerCase() == - widget.control.attrString("labelType", "")!.toLowerCase(), - orElse: () => NavigationRailLabelType.all); - - var leadingCtrls = - widget.children.where((c) => c.name == "leading" && c.isVisible); - var trailingCtrls = - widget.children.where((c) => c.name == "trailing" && c.isVisible); - - var extended = widget.control.attrBool("extended", false)!; - - var rail = withControls( - widget.children - .where((c) => c.isVisible && c.name == null) - .map((c) => c.id), (content, viewModel) { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - debugPrint( - "NavigationRail constraints.maxWidth: ${constraints.maxWidth}"); - debugPrint( - "NavigationRail constraints.maxHeight: ${constraints.maxHeight}"); - - if (constraints.maxHeight == double.infinity && - widget.control.attrDouble("height") == null) { - return const ErrorControl("Error displaying NavigationRail", - description: - "Control's height is unbounded. Either set \"expand\" property, set a fixed \"height\" or nest NavigationRail inside another control with a fixed height."); - } - - return NavigationRail( - labelType: extended ? NavigationRailLabelType.none : labelType, - extended: extended, - elevation: widget.control.attrDouble("elevation"), - selectedLabelTextStyle: parseTextStyle( - Theme.of(context), widget.control, "selectedLabelTextStyle"), - unselectedLabelTextStyle: parseTextStyle(Theme.of(context), - widget.control, "unselectedLabelTextStyle"), - indicatorShape: - parseOutlinedBorder(widget.control, "indicatorShape"), - minWidth: widget.control.attrDouble("minWidth"), - minExtendedWidth: widget.control.attrDouble("minExtendedWidth"), - groupAlignment: widget.control.attrDouble("groupAlignment"), - backgroundColor: widget.control.attrColor("bgColor", context), - indicatorColor: - widget.control.attrColor("indicatorColor", context), - leading: leadingCtrls.isNotEmpty - ? createControl( - widget.control, leadingCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : null, - trailing: trailingCtrls.isNotEmpty - ? createControl( - widget.control, trailingCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : null, - selectedIndex: _selectedIndex, - onDestinationSelected: _destinationChanged, - destinations: viewModel.controlViews.map((destView) { - var label = destView.control.attrString("label", "")!; - var labelContentCtrls = destView.children - .where((c) => c.name == "label_content" && c.isVisible); - - var iconStr = parseIcon(destView.control.attrString("icon")); - var iconCtrls = destView.children - .where((c) => c.name == "icon" && c.isVisible); - var selectedIconStr = - parseIcon(destView.control.attrString("selectedIcon")); - var selectedIconCtrls = destView.children - .where((c) => c.name == "selected_icon" && c.isVisible); - return NavigationRailDestination( - disabled: disabled || destView.control.isDisabled, - padding: parseEdgeInsets(destView.control, "padding"), - indicatorColor: - destView.control.attrColor("indicatorColor", context), - indicatorShape: - parseOutlinedBorder(destView.control, "indicatorShape"), - icon: iconCtrls.isNotEmpty - ? createControl(destView.control, - iconCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : Icon(iconStr), - selectedIcon: selectedIconCtrls.isNotEmpty - ? createControl(destView.control, - selectedIconCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : selectedIconStr != null - ? Icon(selectedIconStr) - : null, - label: labelContentCtrls.isNotEmpty - ? createControl(destView.control, - labelContentCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : Text(label)); - }).toList()); - }, - ); - }); - - return constrainedControl(context, rail, widget.parent, widget.control); + var labelType = parseNavigationRailLabelType( + widget.control.getString("label_type"), NavigationRailLabelType.all)!; + + var extended = widget.control.getBool("extended", false)!; + + var rail = LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + debugPrint( + "NavigationRail constraints.maxWidth: ${constraints.maxWidth}"); + debugPrint( + "NavigationRail constraints.maxHeight: ${constraints.maxHeight}"); + + if (constraints.maxHeight == double.infinity && + widget.control.getDouble("height") == null) { + return const ErrorControl("Error displaying NavigationRail", + description: + "Control's height is unbounded. Either set \"expand\" property, set a fixed \"height\" or nest NavigationRail inside another control with a fixed height."); + } + + return NavigationRail( + labelType: extended ? NavigationRailLabelType.none : labelType, + extended: extended, + elevation: widget.control.getDouble("elevation"), + selectedLabelTextStyle: parseTextStyle( + widget.control.get("selected_label_text_style"), + Theme.of(context)), + unselectedLabelTextStyle: parseTextStyle( + widget.control.get("unselected_label_text_style"), + Theme.of(context)), + indicatorShape: widget.control + .getOutlinedBorder("indicator_shape", Theme.of(context)), + minWidth: widget.control.getDouble("min_width"), + minExtendedWidth: widget.control.getDouble("min_extended_width"), + groupAlignment: widget.control.getDouble("group_alignment"), + backgroundColor: widget.control.getColor("bgcolor", context), + indicatorColor: widget.control.getColor("indicator_color", context), + leading: widget.control.buildWidget("leading"), + trailing: widget.control.buildWidget("trailing"), + selectedIndex: _selectedIndex, + onDestinationSelected: _destinationChanged, + destinations: + widget.control.children("destinations").map((destinationControl) { + destinationControl.notifyParent = true; + var icon = destinationControl.buildWidget("icon") ?? + Icon(parseIcon(destinationControl.getString("icon"))); + var selectedIcon = destinationControl + .buildWidget("selected_icon") ?? + Icon(parseIcon(destinationControl.getString("selected_icon"))); + return NavigationRailDestination( + disabled: disabled || destinationControl.disabled, + padding: destinationControl.getPadding("padding"), + indicatorColor: + destinationControl.getColor("indicator_color", context), + indicatorShape: destinationControl.getOutlinedBorder( + "indicator_shape", Theme.of(context)), + icon: icon, + selectedIcon: selectedIcon, + label: destinationControl.buildTextOrWidget("label", + required: true, + errorWidget: ErrorWidget( + "label (string or visible Control) must be provided"))!); + }).toList(), + ); + }, + ); + + return ConstrainedControl(control: widget.control, child: rail); } } diff --git a/packages/flet/lib/src/controls/outlined_button.dart b/packages/flet/lib/src/controls/outlined_button.dart deleted file mode 100644 index f655153be..000000000 --- a/packages/flet/lib/src/controls/outlined_button.dart +++ /dev/null @@ -1,186 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../flet_control_backend.dart'; -import '../models/control.dart'; -import '../utils/buttons.dart'; -import '../utils/icons.dart'; -import '../utils/launch_url.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'cupertino_button.dart'; -import 'cupertino_dialog_action.dart'; -import 'flet_store_mixin.dart'; - -class OutlinedButtonControl extends StatefulWidget { - final Control? parent; - final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - - const OutlinedButtonControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); - - @override - State createState() => _OutlinedButtonControlState(); -} - -class _OutlinedButtonControlState extends State - with FletStoreMixin { - late final FocusNode _focusNode; - String? _lastFocusValue; - - @override - void initState() { - super.initState(); - _focusNode = FocusNode(); - _focusNode.addListener(_onFocusChange); - } - - @override - void dispose() { - _focusNode.removeListener(_onFocusChange); - _focusNode.dispose(); - super.dispose(); - } - - void _onFocusChange() { - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); - } - - @override - Widget build(BuildContext context) { - debugPrint("Button build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - String text = widget.control.attrString("text", "")!; - IconData? icon = parseIcon(widget.control.attrString("icon")); - Color? iconColor = widget.control.attrColor("iconColor", context); - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); - String url = widget.control.attrString("url", "")!; - String? urlTarget = widget.control.attrString("urlTarget"); - bool onHover = widget.control.attrBool("onHover", false)!; - bool onLongPress = widget.control.attrBool("onLongPress", false)!; - bool autofocus = widget.control.attrBool("autofocus", false)!; - var clipBehavior = - parseClip(widget.control.attrString("clipBehavior"), Clip.none)!; - Function()? onPressed = !disabled - ? () { - debugPrint("Button ${widget.control.id} clicked!"); - if (url != "") { - openWebBrowser(url, webWindowName: urlTarget); - } - widget.backend.triggerControlEvent(widget.control.id, "click"); - } - : null; - - Function()? onLongPressHandler = onLongPress && !disabled - ? () { - debugPrint("Button ${widget.control.id} long pressed!"); - widget.backend.triggerControlEvent(widget.control.id, "long_press"); - } - : null; - - Function(bool)? onHoverHandler = onHover && !disabled - ? (state) { - debugPrint("Button ${widget.control.id} hovered!"); - widget.backend.triggerControlEvent( - widget.control.id, "hover", state.toString()); - } - : null; - - return withPagePlatform((context, platform) { - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - if (adaptive == true && - (platform == TargetPlatform.iOS || - platform == TargetPlatform.macOS)) { - return widget.control.name == "action" && - (widget.parent?.type == "alertdialog" || - widget.parent?.type == "cupertinoalertdialog") - ? CupertinoDialogActionControl( - control: widget.control, - parentDisabled: widget.parentDisabled, - parentAdaptive: adaptive, - children: widget.children, - backend: widget.backend) - : CupertinoButtonControl( - control: widget.control, - parentDisabled: widget.parentDisabled, - parentAdaptive: adaptive, - children: widget.children, - backend: widget.backend); - } - - OutlinedButton? button; - - var theme = Theme.of(context); - - var style = parseButtonStyle(Theme.of(context), widget.control, "style", - defaultForegroundColor: theme.colorScheme.primary, - defaultBackgroundColor: Colors.transparent, - defaultOverlayColor: Colors.transparent, - defaultShadowColor: Colors.transparent, - defaultSurfaceTintColor: Colors.transparent, - defaultElevation: 0, - defaultPadding: const EdgeInsets.all(8), - defaultBorderSide: BorderSide(color: theme.colorScheme.outline), - defaultShape: theme.useMaterial3 - ? const StadiumBorder() - : RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))); - - if (icon != null) { - button = OutlinedButton.icon( - autofocus: autofocus, - focusNode: _focusNode, - onPressed: onPressed, - onLongPress: onLongPressHandler, - clipBehavior: clipBehavior, - style: style, - icon: Icon( - icon, - color: iconColor, - ), - label: Text(text)); - } else if (contentCtrls.isNotEmpty) { - button = OutlinedButton( - autofocus: autofocus, - focusNode: _focusNode, - onPressed: onPressed, - onLongPress: onLongPressHandler, - clipBehavior: clipBehavior, - onHover: onHoverHandler, - style: style, - child: - createControl(widget.control, contentCtrls.first.id, disabled)); - } else { - button = OutlinedButton( - autofocus: autofocus, - focusNode: _focusNode, - style: style, - onPressed: onPressed, - onLongPress: onLongPressHandler, - clipBehavior: clipBehavior, - onHover: onHoverHandler, - child: Text(text)); - } - - var focusValue = widget.control.attrString("focus"); - if (focusValue != null && focusValue != _lastFocusValue) { - _lastFocusValue = focusValue; - _focusNode.requestFocus(); - } - - return constrainedControl(context, button, widget.parent, widget.control); - }); - } -} diff --git a/packages/flet/lib/src/controls/page.dart b/packages/flet/lib/src/controls/page.dart index e0e9728d7..c5504d957 100644 --- a/packages/flet/lib/src/controls/page.dart +++ b/packages/flet/lib/src/controls/page.dart @@ -1,185 +1,70 @@ -import 'dart:async'; -import 'dart:convert'; +import 'dart:ui'; import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; -import 'package:flet/src/models/page_args_model.dart'; -import 'package:flet/src/utils/locale.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:flutter_redux/flutter_redux.dart'; -import 'package:redux/redux.dart'; +import 'package:provider/provider.dart'; -import '../actions.dart'; -import '../flet_app_context.dart'; -import '../flet_app_services.dart'; -import '../flet_control_backend.dart'; -import '../models/app_state.dart'; +import '../flet_backend.dart'; import '../models/control.dart'; -import '../models/control_view_model.dart'; -import '../models/page_media_view_model.dart'; +import '../models/keyboard_event.dart'; +import '../models/multi_view.dart'; +import '../models/page_design.dart'; import '../routing/route_parser.dart'; import '../routing/route_state.dart'; import '../routing/router_delegate.dart'; -import '../utils/alignment.dart'; -import '../utils/box.dart'; -import '../utils/buttons.dart'; -import '../utils/desktop.dart'; -import '../utils/edge_insets.dart'; -import '../utils/images.dart'; -import '../utils/platform.dart'; +import '../services/service_registry.dart'; +import '../utils/locale.dart'; +import '../utils/numbers.dart'; +import '../utils/platform_utils_web.dart' + if (dart.library.io) "../utils/platform_utils_non_web.dart"; +import '../utils/session_store_web.dart' + if (dart.library.io) "../utils/session_store_non_web.dart"; import '../utils/theme.dart'; import '../utils/user_fonts.dart'; import '../widgets/animated_transition_page.dart'; import '../widgets/loading_page.dart'; +import '../widgets/page_context.dart'; import '../widgets/page_media.dart'; -import '../widgets/window_media.dart'; -import 'app_bar.dart'; -import 'create_control.dart'; -import 'cupertino_app_bar.dart'; -import 'flet_store_mixin.dart'; -import 'navigation_drawer.dart'; -import 'scroll_notification_control.dart'; -import 'scrollable_control.dart'; - -enum PageDesign { material, cupertino } - -class RoutesViewModel extends Equatable { - final Control page; - final bool isLoading; - final String error; - final List offstageControls; - final List views; - - const RoutesViewModel( - {required this.page, - required this.isLoading, - required this.error, - required this.offstageControls, - required this.views}); - - static RoutesViewModel fromStore(Store store) { - Control? offstageControl = store.state.controls["page"]!.childIds - .map((childId) => store.state.controls[childId]!) - .firstWhereOrNull((c) => c.type == "offstage"); - - return RoutesViewModel( - page: store.state.controls["page"]!, - isLoading: store.state.isLoading, - error: store.state.error, - offstageControls: offstageControl != null - ? store.state.controls[offstageControl.id]!.childIds - .map((childId) => store.state.controls[childId]!) - .where((c) => c.isVisible) - .toList() - : [], - views: store.state.controls["page"]!.childIds - .map((childId) => store.state.controls[childId]!) - .where((c) => c.type != "offstage" && c.isVisible) - .toList()); - } - - @override - List get props => [page, isLoading, error, offstageControls, views]; -} - -class KeyboardEvent { - final String key; - final bool isShiftPressed; - final bool isControlPressed; - final bool isAltPressed; - final bool isMetaPressed; - - KeyboardEvent( - {required this.key, - required this.isShiftPressed, - required this.isControlPressed, - required this.isAltPressed, - required this.isMetaPressed}); - - Map toJson() => { - 'key': key, - 'shift': isShiftPressed, - 'ctrl': isControlPressed, - 'alt': isAltPressed, - 'meta': isMetaPressed - }; -} +import 'control_widget.dart'; class PageControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final dynamic dispatch; - final FletControlBackend backend; - const PageControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.dispatch, - required this.backend}); + PageControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _PageControlState(); } -class _PageControlState extends State with FletStoreMixin { - bool? _adaptive; - PageDesign _widgetsDesign = PageDesign.material; - TargetPlatform _platform = defaultTargetPlatform; - Brightness? _brightness; - ThemeMode? _themeMode; - Map? _localeConfiguration; - String? _windowTitle; - Color? _windowBgcolor; - double? _windowWidth; - double? _windowHeight; - double? _windowMinWidth; - double? _windowMinHeight; - double? _windowMaxWidth; - double? _windowMaxHeight; - double? _windowTop; - double? _windowLeft; - double? _windowOpacity; - bool? _windowMinimizable; - bool? _windowMaximizable; - bool? _windowFullScreen; - bool? _windowMovable; - bool? _windowResizable; - bool? _windowAlwaysOnTop; - bool? _windowAlwaysOnBottom; - bool? _windowPreventClose; - bool? _windowMinimized; - bool? _windowMaximized; - Alignment? _windowAlignment; - String? _windowBadgeLabel; - String? _windowIcon; - bool? _windowHasShadow; - bool? _windowVisible; - bool? _windowFocused; - String? _windowCenter; - String? _windowClose; - bool? _windowFrameless; - bool? _windowTitleBarHidden; - bool? _windowSkipTaskBar; - double? _windowProgressBar; - bool? _windowIgnoreMouseEvents; +class _PageControlState extends State with WidgetsBindingObserver { final _navigatorKey = GlobalKey(); late final RouteState _routeState; late final SimpleRouterDelegate _routerDelegate; late final RouteParser _routeParser; late final AppLifecycleListener _appLifecycleListener; - String? _prevViewRoutes; + ServiceRegistry? _pageServices; + ServiceRegistry? _userServices; + bool? _prevOnKeyboardEvent; bool _keyboardHandlerSubscribed = false; + String? _prevViewRoutes; + + final Map _multiViews = {}; + bool _registeredFromMultiViews = false; + @override void initState() { + debugPrint("Page.initState: ${widget.control.id}"); super.initState(); + + WidgetsBinding.instance.addObserver(this); + _updateMultiViews(); + _routeParser = RouteParser(); _routeState = RouteState(_routeParser); @@ -199,22 +84,113 @@ class _PageControlState extends State with FletStoreMixin { onPause: () => _handleAppLifecycleTransition('pause'), onDetach: () => _handleAppLifecycleTransition('detach'), onRestart: () => _handleAppLifecycleTransition('restart')); + + _attachKeyboardListenerIfNeeded(); + } + + @override + void didChangeDependencies() { + debugPrint("Page.didChangeDependencies: ${widget.control.id}"); + super.didChangeDependencies(); + + _loadFontsIfNeeded(FletBackend.of(context)); + } + + @override + void didUpdateWidget(covariant PageControl oldWidget) { + debugPrint("Page.didUpdateWidget: ${widget.control.id}"); + super.didUpdateWidget(oldWidget); + _updateMultiViews(); + + // page services + var pageServicesControl = widget.control.child("_page_services"); + if (_pageServices == null && pageServicesControl != null) { + _pageServices = ServiceRegistry( + control: pageServicesControl, + propertyName: "services", + backend: FletBackend.of(context)); + } + + // user services + var userServicesControl = widget.control.child("_user_services"); + if (_userServices == null && userServicesControl != null) { + _userServices = ServiceRegistry( + control: userServicesControl, + propertyName: "services", + backend: FletBackend.of(context)); + } + + _attachKeyboardListenerIfNeeded(); + _loadFontsIfNeeded(FletBackend.of(context)); + } + + @override + void didChangeMetrics() { + _updateMultiViews(); } @override void dispose() { + debugPrint("Page.dispose: ${widget.control.id}"); + WidgetsBinding.instance.removeObserver(this); _routeState.removeListener(_routeChanged); - _routeState.dispose(); + _appLifecycleListener.dispose(); if (_keyboardHandlerSubscribed) { HardwareKeyboard.instance.removeHandler(_handleKeyDown); } - _appLifecycleListener.dispose(); super.dispose(); } + void _updateMultiViews() { + if (!widget.control.backend.multiView) { + return; + } + bool changed = false; + + bool triggerAddViewEvent = SessionStore.get("triggerAddViewEvent") == null; + for (final FlutterView view + in WidgetsBinding.instance.platformDispatcher.views) { + if (!_multiViews.containsKey(view.viewId)) { + var initialData = getViewInitialData(view.viewId); + debugPrint("View initial data ${view.viewId}: $initialData"); + _multiViews[view.viewId] = MultiView( + viewId: view.viewId, flutterView: view, initialData: initialData); + if (triggerAddViewEvent) { + widget.control.backend.triggerControlEventById( + widget.control.id, + "multi_view_add", + {"view_id": view.viewId, "initial_data": initialData}); + } + changed = true; + } + } + for (var viewId in _multiViews.keys.toList()) { + if (!WidgetsBinding.instance.platformDispatcher.views + .any((view) => view.viewId == viewId)) { + _multiViews.remove(viewId); + if (triggerAddViewEvent) { + widget.control.backend.triggerControlEventById( + widget.control.id, "multi_view_remove", viewId); + } + changed = true; + } + } + SessionStore.set("triggerAddViewEvent", "true"); + if (changed && !_registeredFromMultiViews) { + _registeredFromMultiViews = true; + widget.control.backend.onRouteUpdated("/"); + } else { + // re-draw + setState(() {}); + } + } + void _routeChanged() { - widget.dispatch(SetPageRouteAction( - _routeState.route, FletAppServices.of(context).server)); + FletBackend.of(context).onRouteUpdated(_routeState.route); + } + + void _handleAppLifecycleTransition(String state) { + widget.control.triggerEvent("app_lifecycle_state_change", state); } bool _handleKeyDown(KeyEvent e) { @@ -234,994 +210,292 @@ class _PageControlState extends State with FletStoreMixin { LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftRight ].contains(k)) { - widget.backend.triggerControlEvent( - "page", + widget.control.triggerEvent( "keyboard_event", - json.encode(KeyboardEvent( + KeyboardEvent( key: k.keyLabel, isAltPressed: HardwareKeyboard.instance.isAltPressed, isControlPressed: HardwareKeyboard.instance.isControlPressed, isShiftPressed: HardwareKeyboard.instance.isShiftPressed, isMetaPressed: HardwareKeyboard.instance.isMetaPressed) - .toJson())); + .toMap()); } } return false; } - void _handleAppLifecycleTransition(String state) { - widget.backend - .triggerControlEvent("page", "app_lifecycle_state_change", state); + void _attachKeyboardListenerIfNeeded() { + var onKeyboardEvent = widget.control.getBool("on_keyboard_event", false); + if (onKeyboardEvent != _prevOnKeyboardEvent) { + if (onKeyboardEvent == true && !_keyboardHandlerSubscribed) { + HardwareKeyboard.instance.addHandler(_handleKeyDown); + _keyboardHandlerSubscribed = true; + } else if (onKeyboardEvent == false && _keyboardHandlerSubscribed) { + HardwareKeyboard.instance.removeHandler(_handleKeyDown); + _keyboardHandlerSubscribed = false; + } + _prevOnKeyboardEvent = onKeyboardEvent; + } + } + + Future _loadFontsIfNeeded(FletBackend backend) async { + final fonts = widget.control.getFonts("fonts", {})!; + for (final entry in fonts.entries) { + final fontFamily = entry.key; + final fontUrl = entry.value; + var assetSrc = backend.getAssetSource(fontUrl); + try { + if (assetSrc.isFile) { + await UserFonts.loadFontFromFile(fontFamily, fontUrl); + } else { + await UserFonts.loadFontFromUrl(fontFamily, fontUrl); + } + } catch (e) { + debugPrint("Error loading font $fontFamily: $e"); + } + } } @override Widget build(BuildContext context) { - debugPrint("Page build: ${widget.control.id}"); - - //debugDumpRenderTree(); + debugPrint("Page.build: ${widget.control.id}"); // clear hrefs index - FletAppServices.of(context).globalKeys.clear(); + FletBackend.of(context).globalKeys.clear(); // page route - var route = widget.control.attrString("route"); - if (_routeState.route != route && route != null) { - // route updated - _routeState.route = route; + var route = widget.control.getString("route"); + if (route != null && _routeState.route != route) { + // update route + WidgetsBinding.instance.addPostFrameCallback((_) { + _routeState.route = route; + }); } - _platform = TargetPlatform.values.firstWhere( + if (!widget.control.backend.multiView) { + // single page mode + return _buildApp(widget.control, null); + } else { + // multi-view mode + var appStatus = context + .select((backend) => + (isLoading: backend.isLoading, error: backend.error)); + var appStartupScreenMessage = + FletBackend.of(context).appStartupScreenMessage ?? ""; + + List views = []; + for (var view in _multiViews.entries) { + var multiViewControl = widget.control + .children("multi_views") + .firstWhereOrNull((v) => v.get("view_id") == view.key); + + var viewControl = multiViewControl?.children("views").firstOrNull; + + Widget viewChild = SizedBox( + width: 100, + height: 100, + child: viewControl != null + ? ControlWidget(control: viewControl) + : Stack(children: [ + const PageMedia(), + LoadingPage( + isLoading: appStatus.isLoading, + message: appStatus.isLoading + ? appStartupScreenMessage + : appStatus.error, + ) + ]), + ); + + viewChild = _buildApp(multiViewControl ?? widget.control, viewChild); + views.add(View(view: view.value.flutterView, child: viewChild)); + } + return ViewCollection(views: views); + } + } + + Widget _buildApp(Control control, Widget? home) { + var platform = TargetPlatform.values.firstWhere( (a) => a.name.toLowerCase() == - widget.control.attrString("platform", "")!.toLowerCase(), + control.getString("platform", "")!.toLowerCase(), orElse: () => defaultTargetPlatform); - _adaptive = widget.control.attrBool("adaptive"); - - _widgetsDesign = _adaptive == true && - (_platform == TargetPlatform.iOS || - _platform == TargetPlatform.macOS) + var widgetsDesign = control.adaptive == true && + (platform == TargetPlatform.iOS || platform == TargetPlatform.macOS) ? PageDesign.cupertino : PageDesign.material; // theme - _themeMode = ThemeMode.values.firstWhereOrNull((t) => - t.name.toLowerCase() == - widget.control.attrString("themeMode", "")!.toLowerCase()) ?? - FletAppContext.of(context)?.themeMode; - - _localeConfiguration = - parseLocaleConfiguration(widget.control, "localeConfiguration"); - - // keyboard handler - var onKeyboardEvent = widget.control.attrBool("onKeyboardEvent", false)!; - if (onKeyboardEvent && !_keyboardHandlerSubscribed) { - HardwareKeyboard.instance.addHandler(_handleKeyDown); - _keyboardHandlerSubscribed = true; - } - - // window params - var windowTitle = widget.control.attrString("title", "")!; - var windowBgcolor = widget.control.attrColor("windowBgcolor", context); - var windowWidth = widget.control.attrDouble("windowWidth"); - var windowHeight = widget.control.attrDouble("windowHeight"); - var windowMinWidth = widget.control.attrDouble("windowMinWidth"); - var windowMinHeight = widget.control.attrDouble("windowMinHeight"); - var windowMaxWidth = widget.control.attrDouble("windowMaxWidth"); - var windowMaxHeight = widget.control.attrDouble("windowMaxHeight"); - var windowTop = widget.control.attrDouble("windowTop"); - var windowLeft = widget.control.attrDouble("windowLeft"); - var windowCenter = widget.control.attrString("windowCenter"); - var windowClose = widget.control.attrString("windowClose"); - var windowFullScreen = widget.control.attrBool("windowFullScreen"); - var windowMinimized = widget.control.attrBool("windowMinimized"); - var windowMaximized = widget.control.attrBool("windowMaximized"); - var windowAlignment = parseAlignment(widget.control, "windowAlignment"); - var windowBadgeLabel = widget.control.attrString("windowBadgeLabel"); - var windowIcon = widget.control.attrString("windowIcon"); - var windowHasShadow = widget.control.attrBool("windowShadow"); - var windowOpacity = widget.control.attrDouble("windowOpacity"); - var windowMinimizable = widget.control.attrBool("windowMinimizable"); - var windowMaximizable = widget.control.attrBool("windowMaximizable"); - var windowAlwaysOnTop = widget.control.attrBool("windowAlwaysOnTop"); - var windowAlwaysOnBottom = widget.control.attrBool("windowAlwaysOnBottom"); - var windowResizable = widget.control.attrBool("windowResizable"); - var windowMovable = widget.control.attrBool("windowMovable"); - var windowPreventClose = widget.control.attrBool("windowPreventClose"); - var windowTitleBarHidden = widget.control.attrBool("windowTitleBarHidden"); - var windowTitleBarButtonsHidden = - widget.control.attrBool("windowTitleBarButtonsHidden", false)!; - var windowVisible = widget.control.attrBool("windowVisible"); - var windowFocused = widget.control.attrBool("windowFocused"); - var windowDestroy = widget.control.attrBool("windowDestroy"); - var windowWaitUntilReadyToShow = - widget.control.attrBool("windowWaitUntilReadyToShow"); - var windowSkipTaskBar = widget.control.attrBool("windowSkipTaskBar"); - var windowFrameless = widget.control.attrBool("windowFrameless"); - var windowProgressBar = widget.control.attrDouble("windowProgressBar"); - var windowIgnoreMouseEvents = - widget.control.attrBool("windowIgnoreMouseEvents"); - - updateWindow(PageArgsModel? pageArgs) async { - try { - // windowTitle - if (_windowTitle != windowTitle) { - setWindowTitle(windowTitle); - _windowTitle = windowTitle; - } - - // windowBgcolor - if (_windowBgcolor != windowBgcolor && windowBgcolor != null) { - setWindowBackgroundColor(windowBgcolor); - _windowBgcolor = windowBgcolor; - } - - // window size - if ((windowWidth != null || windowHeight != null) && - (windowWidth != _windowWidth || windowHeight != _windowHeight) && - windowFullScreen != true && - (defaultTargetPlatform != TargetPlatform.macOS || - (defaultTargetPlatform == TargetPlatform.macOS && - windowMaximized != true && - windowMinimized != true))) { - debugPrint("setWindowSize: $windowWidth, $windowHeight"); - await setWindowSize(windowWidth, windowHeight); - _windowWidth = windowWidth; - _windowHeight = windowHeight; - } - - // window min size - if ((windowMinWidth != null || windowMinHeight != null) && - (windowMinWidth != _windowMinWidth || - windowMinHeight != _windowMinHeight)) { - debugPrint("setWindowMinSize: $windowMinWidth, $windowMinHeight"); - await setWindowMinSize(windowMinWidth, windowMinHeight); - _windowMinWidth = windowMinWidth; - _windowMinHeight = windowMinHeight; - } - - // window max size - if ((windowMaxWidth != null || windowMaxHeight != null) && - (windowMaxWidth != _windowMaxWidth || - windowMaxHeight != _windowMaxHeight)) { - debugPrint("setWindowMaxSize: $windowMaxWidth, $windowMaxHeight"); - await setWindowMaxSize(windowMaxWidth, windowMaxHeight); - _windowMaxWidth = windowMaxWidth; - _windowMaxHeight = windowMaxHeight; - } - - // window position - if ((windowTop != null || windowLeft != null) && - (windowTop != _windowTop || windowLeft != _windowLeft) && - windowFullScreen != true && - (windowCenter == null || windowCenter == "") && - (defaultTargetPlatform != TargetPlatform.macOS || - (defaultTargetPlatform == TargetPlatform.macOS && - windowMaximized != true && - windowMinimized != true))) { - debugPrint("setWindowPosition: $windowTop, $windowLeft"); - await setWindowPosition(windowTop, windowLeft); - _windowTop = windowTop; - _windowLeft = windowLeft; - } - - // windowOpacity - if (windowOpacity != null && windowOpacity != _windowOpacity) { - await setWindowOpacity(windowOpacity); - _windowOpacity = windowOpacity; - } - - // windowMinimizable - if (windowMinimizable != null && - windowMinimizable != _windowMinimizable) { - await setWindowMinimizability(windowMinimizable); - _windowMinimizable = windowMinimizable; - } - - // windowMinimized - if (windowMinimized != _windowMinimized) { - if (windowMinimized == true) { - await minimizeWindow(); - } else if (windowMinimized == false && windowMaximized == false) { - await restoreWindow(); - } - _windowMinimized = windowMinimized; - } - - // windowMaximizable - if (windowMaximizable != null && - windowMaximizable != _windowMaximizable) { - await setWindowMaximizability(windowMaximizable); - _windowMaximizable = windowMaximizable; - } - - // windowMaximized - if (windowMaximized != _windowMaximized) { - if (windowMaximized == true) { - await maximizeWindow(); - } else if (windowMaximized == false) { - await unmaximizeWindow(); - } - _windowMaximized = windowMaximized; - } - - // windowAlignment - if (windowAlignment != null && windowAlignment != _windowAlignment) { - await setWindowAlignment(windowAlignment); - _windowAlignment = windowAlignment; - } - - // windowBadgeLabel - if (windowBadgeLabel != null && windowBadgeLabel != _windowBadgeLabel) { - await setWindowBadgeLabel(windowBadgeLabel); - _windowBadgeLabel = windowBadgeLabel; - } - - // windowIcon - if (windowIcon != null && windowIcon != _windowIcon) { - if (pageArgs == null) { - await setWindowIcon(windowIcon); - } else { - var iconAssetSrc = - getAssetSrc(windowIcon, pageArgs.pageUri!, pageArgs.assetsDir); - await setWindowIcon(iconAssetSrc.path); - } - _windowIcon = windowIcon; - } - - // windowHasShadow - if (windowHasShadow != null && windowHasShadow != _windowHasShadow) { - await setWindowShadow(windowHasShadow); - _windowHasShadow = windowHasShadow; - } - - // windowResizable - if (windowResizable != null && windowResizable != _windowResizable) { - await setWindowResizability(windowResizable); - _windowResizable = windowResizable; - } - - // windowMovable - if (windowMovable != null && windowMovable != _windowMovable) { - await setWindowMovability(windowMovable); - _windowMovable = windowMovable; - } - - // windowFullScreen - if (windowFullScreen != null && windowFullScreen != _windowFullScreen) { - await setWindowFullScreen(windowFullScreen); - _windowFullScreen = windowFullScreen; - } - - // windowAlwaysOnTop - if (windowAlwaysOnTop != null && - windowAlwaysOnTop != _windowAlwaysOnTop) { - await setWindowAlwaysOnTop(windowAlwaysOnTop); - _windowAlwaysOnTop = windowAlwaysOnTop; - } + var themeMode = control.getThemeMode("theme_mode") ?? + PageContext.of(context)?.themeMode; - // windowAlwaysOnBottom - if (windowAlwaysOnBottom != null && - windowAlwaysOnBottom != _windowAlwaysOnBottom) { - await setWindowAlwaysOnBottom(windowAlwaysOnBottom); - _windowAlwaysOnBottom = windowAlwaysOnBottom; - } + var localeConfiguration = + control.getLocaleConfiguration("locale_configuration"); - // windowPreventClose - if (windowPreventClose != null && - windowPreventClose != _windowPreventClose) { - await setWindowPreventClose(windowPreventClose); - _windowPreventClose = windowPreventClose; - } + var localizationsDelegates = const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ]; - // windowTitleBarHidden - if (windowTitleBarHidden != null && - windowTitleBarHidden != _windowTitleBarHidden) { - await setWindowTitleBarVisibility( - windowTitleBarHidden, windowTitleBarButtonsHidden); - _windowTitleBarHidden = windowTitleBarHidden; - } + var brightness = context.select( + (backend) => backend.platformBrightness); - // windowVisible - if (windowVisible != _windowVisible) { - if (windowVisible == true) { - await showWindow(); - } else if (windowVisible == false) { - await hideWindow(); - } - _windowVisible = windowVisible; - } - - // windowFocused - if (windowFocused != _windowFocused) { - if (windowFocused == true) { - await focusWindow(); - } else if (windowFocused == false) { - await blurWindow(); - } - _windowFocused = windowFocused; - } - - // windowCenter - if (windowCenter != _windowCenter && windowFullScreen != true) { - await centerWindow(); - _windowCenter = windowCenter; - } - - // windowFrameless - if (windowFrameless != _windowFrameless && windowFrameless == true) { - await setWindowFrameless(); - _windowFrameless = windowFrameless; - } - - // windowProgressBar - if (windowProgressBar != null && - windowProgressBar != _windowProgressBar) { - await setWindowProgressBar(windowProgressBar); - _windowProgressBar = windowProgressBar; - } - - // windowSkipTaskBar - if (windowSkipTaskBar != null && - windowSkipTaskBar != _windowSkipTaskBar) { - await setWindowSkipTaskBar(windowSkipTaskBar); - _windowSkipTaskBar = windowSkipTaskBar; - } - - // windowClose - if (windowClose != _windowClose) { - await closeWindow(); - _windowClose = windowClose; - } - - // windowDestroy - if (windowDestroy == true) { - await destroyWindow(); - } + var windowTitle = control.getString("title", "")!; - // window waitUntilReadyToShow - if (windowWaitUntilReadyToShow == true) { - await waitUntilReadyToShow(); - } + var newLightTheme = control.getTheme("theme", context, Brightness.light); + var newDarkTheme = control.getString("dark_theme") == null + ? control.getTheme("theme", context, Brightness.dark) + : parseTheme(control.get("dark_theme"), context, Brightness.dark); - // windowIgnoreMouseEvents - if (windowIgnoreMouseEvents != null && - windowIgnoreMouseEvents != _windowIgnoreMouseEvents) { - await setIgnoreMouseEvents(windowIgnoreMouseEvents); - _windowIgnoreMouseEvents = windowIgnoreMouseEvents; - } - } catch (e) { - debugPrint("ERROR updating window: $e"); - } + var lightTheme = control.get("_lightTheme"); + if (lightTheme == null || !themesEqual(lightTheme!, newLightTheme)) { + control.updateProperties({"_lightTheme": newLightTheme}, python: false); + lightTheme = newLightTheme; } - return withPageArgs((context, pageArgs) { - updateWindow(pageArgs); - debugPrint("Page fonts build: ${widget.control.id}"); - - // load custom fonts - parseFonts(widget.control, "fonts").forEach((fontFamily, fontUrl) { - var assetSrc = - getAssetSrc(fontUrl, pageArgs.pageUri!, pageArgs.assetsDir); - - if (assetSrc.isFile) { - UserFonts.loadFontFromFile(fontFamily, assetSrc.path); - } else { - UserFonts.loadFontFromUrl(fontFamily, assetSrc.path); - } - }); - - return StoreConnector( - distinct: true, - converter: (store) => PageMediaViewModel.fromStore(store), - builder: (context, media) { - debugPrint("MaterialApp.router build: ${widget.control.id}"); - - _brightness = media.displayBrightness; + var darkTheme = control.get("_darkTheme"); + if (darkTheme == null || !themesEqual(darkTheme!, newDarkTheme)) { + control.updateProperties({"_darkTheme": newDarkTheme}, python: false); + darkTheme = newDarkTheme; + } - return FletAppContext( - themeMode: _themeMode, - child: _widgetsDesign == PageDesign.cupertino - ? CupertinoApp.router( - debugShowCheckedModeBanner: false, - showSemanticsDebugger: widget.control - .attrBool("showSemanticsDebugger", false)!, - routerDelegate: _routerDelegate, - routeInformationParser: _routeParser, - title: windowTitle, - theme: _themeMode == ThemeMode.light || - ((_themeMode == null || - _themeMode == ThemeMode.system) && - _brightness == Brightness.light) - ? parseCupertinoTheme( - widget.control, "theme", Brightness.light) - : widget.control.attrString("darkTheme") != null - ? parseCupertinoTheme(widget.control, - "darkTheme", Brightness.dark) - : parseCupertinoTheme( - widget.control, "theme", Brightness.dark), - localizationsDelegates: const [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: _localeConfiguration != null - ? _localeConfiguration!["supportedLocales"] - : [const Locale('en', 'US')], - locale: _localeConfiguration != null - ? (_localeConfiguration?["locale"]) - : null, - ) - : MaterialApp.router( - debugShowCheckedModeBanner: false, - showSemanticsDebugger: widget.control - .attrBool("showSemanticsDebugger", false)!, - routerDelegate: _routerDelegate, - routeInformationParser: _routeParser, - title: windowTitle, - localizationsDelegates: const [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: _localeConfiguration != null - ? _localeConfiguration!["supportedLocales"] - : [const Locale('en', 'US')], - locale: _localeConfiguration != null - ? (_localeConfiguration?["locale"]) - : null, - theme: parseTheme( - widget.control, "theme", Brightness.light), - darkTheme: widget.control.attrString("darkTheme") == - null - ? parseTheme( - widget.control, "theme", Brightness.dark) - : parseTheme( - widget.control, "darkTheme", Brightness.dark), - themeMode: _themeMode, - )); - }); - }); + var cupertinoTheme = themeMode == ThemeMode.light || + ((themeMode == null || themeMode == ThemeMode.system) && + brightness == Brightness.light) + ? parseCupertinoTheme(control.get("theme"), context, Brightness.light) + : control.getString("dark_theme") != null + ? control.getCupertinoTheme("dark_theme", context, Brightness.dark) + : control.getCupertinoTheme("theme", context, Brightness.dark); + + var showSemanticsDebugger = + control.getBool("show_semantics_debugger", false)!; + + var app = widgetsDesign == PageDesign.cupertino + ? home != null + ? CupertinoApp( + debugShowCheckedModeBanner: false, + showSemanticsDebugger: showSemanticsDebugger, + title: windowTitle, + theme: cupertinoTheme, + supportedLocales: localeConfiguration.supportedLocales, + locale: localeConfiguration.locale, + localizationsDelegates: localizationsDelegates, + home: home, + ) + : CupertinoApp.router( + debugShowCheckedModeBanner: false, + showSemanticsDebugger: showSemanticsDebugger, + routerDelegate: _routerDelegate, + routeInformationParser: _routeParser, + title: windowTitle, + theme: cupertinoTheme, + localizationsDelegates: localizationsDelegates, + supportedLocales: localeConfiguration.supportedLocales, + locale: localeConfiguration.locale, + ) + : home != null + ? MaterialApp( + debugShowCheckedModeBanner: false, + showSemanticsDebugger: showSemanticsDebugger, + title: windowTitle, + theme: lightTheme, + darkTheme: darkTheme, + themeMode: themeMode, + supportedLocales: localeConfiguration.supportedLocales, + locale: localeConfiguration.locale, + localizationsDelegates: localizationsDelegates, + home: home, + ) + : MaterialApp.router( + debugShowCheckedModeBanner: false, + showSemanticsDebugger: showSemanticsDebugger, + routerDelegate: _routerDelegate, + routeInformationParser: _routeParser, + title: windowTitle, + theme: lightTheme, + darkTheme: darkTheme, + themeMode: themeMode, + localizationsDelegates: localizationsDelegates, + supportedLocales: localeConfiguration.supportedLocales, + locale: localeConfiguration.locale, + ); + return PageContext( + themeMode: themeMode, + brightness: brightness, + widgetsDesign: widgetsDesign, + child: app, + ); } Widget _buildNavigator( BuildContext context, GlobalKey navigatorKey) { debugPrint("Page navigator build: ${widget.control.id}"); - return StoreConnector( - distinct: true, - converter: (store) => RoutesViewModel.fromStore(store), - // onWillChange: (prev, next) { - // debugPrint("Page navigator.onWillChange(): $prev, $next"); - // }, - builder: (context, routesView) { - debugPrint("_buildNavigator build"); - - var showAppStartupScreen = - FletAppServices.of(context).showAppStartupScreen ?? false; - var appStartupScreenMessage = - FletAppServices.of(context).appStartupScreenMessage ?? ""; - - List> pages = []; - if (routesView.views.isEmpty) { - pages.add(AnimatedTransitionPage( + var showAppStartupScreen = + FletBackend.of(context).showAppStartupScreen ?? false; + var appStartupScreenMessage = + FletBackend.of(context).appStartupScreenMessage ?? ""; + + var appStatus = + context.select( + (backend) => (isLoading: backend.isLoading, error: backend.error)); + + var views = widget.control.children("views"); + List> pages = []; + if (views.isEmpty) { + pages.add(AnimatedTransitionPage( + fadeTransition: true, + duration: Duration.zero, + child: showAppStartupScreen + ? Stack(children: [ + const PageMedia(), + LoadingPage( + isLoading: appStatus.isLoading, + message: appStatus.isLoading + ? appStartupScreenMessage + : appStatus.error, + ) + ]) + : const Scaffold( + body: PageMedia(), + ))); + } else { + String viewRoutes = + views.map((v) => v.getString("route", v.id.toString())).join(); + + pages = views.map((view) { + var key = ValueKey(view.getString("route", view.id.toString())); + var child = ControlWidget(control: view); + + //debugPrint("ROUTES: $_prevViewRoutes $viewRoutes"); + + return _prevViewRoutes == null + ? AnimatedTransitionPage( + key: key, + child: child, fadeTransition: true, duration: Duration.zero, - child: showAppStartupScreen - ? Stack(children: [ - const PageMedia(), - LoadingPage( - isLoading: routesView.isLoading, - message: routesView.isLoading - ? appStartupScreenMessage - : routesView.error, - ) - ]) - : const Scaffold( - body: PageMedia(), - ))); - } else { - Widget? loadingPage; - // offstage - overlayWidgets(String viewId) { - List overlayWidgets = []; - - if (viewId == routesView.views.last.id) { - overlayWidgets.addAll(routesView.offstageControls - .where((c) => !c.isNonVisual) - .map((c) => createControl( - routesView.page, c.id, routesView.page.isDisabled, - parentAdaptive: _adaptive))); - overlayWidgets.add(const PageMedia()); - } - - if (viewId == routesView.views.first.id && isDesktopPlatform()) { - overlayWidgets.add(WindowMedia(dispatch: widget.dispatch)); - } - - return overlayWidgets; - } - - if ((routesView.isLoading || routesView.error != "") && - showAppStartupScreen) { - loadingPage = LoadingPage( - isLoading: routesView.isLoading, - message: routesView.isLoading - ? appStartupScreenMessage - : routesView.error, - ); - } - - String viewRoutes = routesView.views - .map((v) => v.attrString("route") ?? v.id) - .join(); - - pages = routesView.views.map((view) { - var key = ValueKey(view.attrString("route") ?? view.id); - var child = ViewControl( - parent: routesView.page, - viewId: view.id, - overlayWidgets: overlayWidgets(view.id), - loadingPage: loadingPage, - backend: widget.backend, - parentAdaptive: _adaptive, - widgetsDesign: _widgetsDesign, - brightness: _brightness, - themeMode: _themeMode, - isRootView: view.id == routesView.views.first.id, - ); - - //debugPrint("ROUTES: $_prevViewRoutes $viewRoutes"); - - return _prevViewRoutes == null - ? AnimatedTransitionPage( - key: key, - child: child, - fadeTransition: true, - duration: Duration.zero, - ) - : AnimatedTransitionPage( - key: key, - child: child, - fullscreenDialog: - view.attrBool("fullscreenDialog", false)!); - }).toList(); - - _prevViewRoutes = viewRoutes; - } - - Widget nextChild = Navigator( - key: navigatorKey, - pages: pages, - onDidRemovePage: (page) { - if (page.key != null) { - widget.backend.triggerControlEvent( - "page", "view_pop", (page.key as ValueKey).value); - } - }); - - // wrap navigator into non-visual offstage controls - for (var c - in routesView.offstageControls.where((c) => c.isNonVisual)) { - nextChild = createControl( - routesView.page, c.id, routesView.page.isDisabled, - parentAdaptive: _adaptive, nextChild: nextChild); - } - - return nextChild; - }); - } -} - -class ViewControl extends StatefulWidget { - final Control parent; - final String viewId; - final List overlayWidgets; - final Widget? loadingPage; - final FletControlBackend backend; - final bool? parentAdaptive; - final PageDesign widgetsDesign; - final Brightness? brightness; - final ThemeMode? themeMode; - final bool isRootView; - - ViewControl( - {Key? key, - required this.parent, - required this.viewId, - required this.overlayWidgets, - required this.loadingPage, - required this.backend, - required this.parentAdaptive, - required this.widgetsDesign, - required this.brightness, - required this.themeMode, - required this.isRootView}) - : super(key: ValueKey("control_$viewId")); - - @override - State createState() => _ViewControlState(); -} - -class _ViewControlState extends State with FletStoreMixin { - final scaffoldKey = GlobalKey(); - Completer? _popCompleter; - - @override - Widget build(BuildContext context) { - return StoreConnector( - distinct: true, - converter: (store) { - return ControlViewModel.fromStore(store, widget.viewId); - }, - ignoreChange: (state) { - return state.controls[widget.viewId] == null; - }, - // onWillChange: (prev, next) { - // debugPrint("View StoreConnector.onWillChange(): $prev, $next"); - // }, - builder: (context, controlView) { - debugPrint("View build"); - - if (controlView == null) { - return const SizedBox.shrink(); - } - - var control = controlView.control; - var children = controlView.children; - - var adaptive = control.attrBool("adaptive") ?? widget.parentAdaptive; - - final spacing = control.attrDouble("spacing", 10)!; - final mainAlignment = parseMainAxisAlignment( - control.attrString("verticalAlignment"), - MainAxisAlignment.start)!; - final crossAlignment = parseCrossAxisAlignment( - control.attrString("horizontalAlignment"), - CrossAxisAlignment.start)!; - final fabLocation = parseFloatingActionButtonLocation( - control, "floatingActionButtonLocation"); - final canPop = control.attrBool("canPop", true)!; - - widget.backend.subscribeMethods(control.id, (methodName, args) async { - debugPrint("View.onMethod(${control.id})"); - if (methodName == "confirm_pop") { - _popCompleter?.complete(bool.tryParse(args["shouldPop"] ?? "")); - widget.backend.unsubscribeMethods(control.id); - } - return null; - }); - - Control? appBar; - Control? cupertinoAppBar; - Control? bottomAppBar; - Control? fab; - Control? navBar; - Control? drawer; - Control? endDrawer; - List controls = []; - bool firstControl = true; - - for (var ctrl in children.where((c) => c.isVisible)) { - if (ctrl.type == "appbar") { - appBar = ctrl; - continue; - } else if (ctrl.type == "cupertinoappbar") { - cupertinoAppBar = ctrl; - continue; - } else if (ctrl.type == "bottomappbar") { - bottomAppBar = ctrl; - continue; - } else if (ctrl.name == "fab") { - fab = ctrl; - continue; - } else if (ctrl.type == "navigationbar" || - ctrl.type == "cupertinonavigationbar") { - navBar = ctrl; - continue; - } else if (ctrl.type == "navigationdrawer" && - ctrl.name == "drawer_start") { - drawer = ctrl; - continue; - } else if (ctrl.type == "navigationdrawer" && - ctrl.name == "drawer_end") { - endDrawer = ctrl; - continue; - } - // spacer between displayed controls - else if (spacing > 0 && - !firstControl && - mainAlignment != MainAxisAlignment.spaceAround && - mainAlignment != MainAxisAlignment.spaceBetween && - mainAlignment != MainAxisAlignment.spaceEvenly) { - controls.add(SizedBox(height: spacing)); - } - firstControl = false; + ) + : AnimatedTransitionPage( + key: key, + child: child, + fullscreenDialog: view.getBool("fullscreen_dialog", false)!); + }).toList(); + + _prevViewRoutes = viewRoutes; + } - // displayed control - controls.add(createControl(control, ctrl.id, control.isDisabled, - parentAdaptive: adaptive)); + return Navigator( + key: navigatorKey, + pages: pages, + onDidRemovePage: (page) { + if (page.key != null) { + widget.control.triggerEvent( + "view_pop", {"route": (page.key as ValueKey).value}); } - - List childIds = [ - appBar?.id, - cupertinoAppBar?.id, - drawer?.id, - endDrawer?.id - ].nonNulls.toList(); - - final textDirection = widget.parent.attrBool("rtl", false)! - ? TextDirection.rtl - : TextDirection.ltr; - - return withControls(childIds, (context, childrenViews) { - debugPrint("Route view build: ${widget.viewId}"); - - var appBarView = childrenViews.controlViews - .firstWhereOrNull((v) => v.control.id == (appBar?.id ?? "")); - var cupertinoAppBarView = childrenViews.controlViews - .firstWhereOrNull( - (v) => v.control.id == (cupertinoAppBar?.id ?? "")); - var drawerView = childrenViews.controlViews - .firstWhereOrNull((v) => v.control.id == (drawer?.id ?? "")); - var endDrawerView = childrenViews.controlViews - .firstWhereOrNull((v) => v.control.id == (endDrawer?.id ?? "")); - - var column = Column( - mainAxisAlignment: mainAlignment, - crossAxisAlignment: crossAlignment, - children: controls); - - Widget child = ScrollableControl( - control: control, - scrollDirection: Axis.vertical, - backend: widget.backend, - parentAdaptive: adaptive, - child: column); - - if (control.attrBool("onScroll", false)!) { - child = ScrollNotificationControl( - control: control, backend: widget.backend, child: child); - } - - final bool? drawerOpened = widget.parent.state["drawerOpened"]; - final bool? endDrawerOpened = - widget.parent.state["endDrawerOpened"]; - - void dismissDrawer(String id) { - widget.backend.updateControlState(id, {"open": "false"}); - widget.backend.triggerControlEvent(id, "dismiss"); - } - - WidgetsBinding.instance.addPostFrameCallback((_) { - if (drawerView != null) { - if (scaffoldKey.currentState?.isDrawerOpen == false && - drawerOpened == true) { - widget.parent.state["drawerOpened"] = false; - dismissDrawer(drawerView.control.id); - } - if (drawerView.control.attrBool("open", false)! && - drawerOpened != true) { - if (scaffoldKey.currentState?.isEndDrawerOpen == true) { - scaffoldKey.currentState?.closeEndDrawer(); - } - Future.delayed(const Duration(milliseconds: 1)).then((value) { - scaffoldKey.currentState?.openDrawer(); - widget.parent.state["drawerOpened"] = true; - }); - } else if (!drawerView.control.attrBool("open", false)! && - drawerOpened == true) { - scaffoldKey.currentState?.closeDrawer(); - widget.parent.state["drawerOpened"] = false; - } - } - if (endDrawerView != null) { - if (scaffoldKey.currentState?.isEndDrawerOpen == false && - endDrawerOpened == true) { - widget.parent.state["endDrawerOpened"] = false; - dismissDrawer(endDrawerView.control.id); - } - if (endDrawerView.control.attrBool("open", false)! && - endDrawerOpened != true) { - if (scaffoldKey.currentState?.isDrawerOpen == true) { - scaffoldKey.currentState?.closeDrawer(); - } - Future.delayed(const Duration(milliseconds: 1)).then((value) { - scaffoldKey.currentState?.openEndDrawer(); - widget.parent.state["endDrawerOpened"] = true; - }); - } else if (!endDrawerView.control.attrBool("open", false)! && - endDrawerOpened == true) { - scaffoldKey.currentState?.closeEndDrawer(); - widget.parent.state["endDrawerOpened"] = false; - } - } - }); - - var bnb = navBar ?? bottomAppBar; - - var bar = appBarView != null - ? widget.widgetsDesign == PageDesign.cupertino - ? CupertinoAppBarControl( - parent: control, - control: appBarView.control, - children: appBarView.children, - parentDisabled: control.isDisabled, - parentAdaptive: adaptive) - : AppBarControl( - parent: control, - control: appBarView.control, - children: appBarView.children, - parentDisabled: control.isDisabled, - parentAdaptive: adaptive, - height: appBarView.control - .attrDouble("toolbarHeight", kToolbarHeight)!) - : cupertinoAppBarView != null - ? CupertinoAppBarControl( - parent: control, - control: cupertinoAppBarView.control, - children: cupertinoAppBarView.children, - parentDisabled: control.isDisabled, - parentAdaptive: adaptive, - ) as ObstructingPreferredSizeWidget - : null; - - Widget body = Stack(children: [ - SizedBox.expand( - child: Container( - padding: parseEdgeInsets( - control, "padding", const EdgeInsets.all(10))!, - child: child)), - ...widget.overlayWidgets - ]); - - var materialTheme = widget.themeMode == ThemeMode.light || - ((widget.themeMode == null || - widget.themeMode == ThemeMode.system) && - widget.brightness == Brightness.light) - ? parseTheme(widget.parent, "theme", Brightness.light) - : widget.parent.attrString("darkTheme") != null - ? parseTheme(widget.parent, "darkTheme", Brightness.dark) - : parseTheme(widget.parent, "theme", Brightness.dark); - - Widget scaffold = Scaffold( - key: bar == null || bar is AppBarControl ? scaffoldKey : null, - backgroundColor: control.attrColor("bgcolor", context) ?? - CupertinoTheme.of(context).scaffoldBackgroundColor, - appBar: bar is AppBarControl ? bar : null, - drawer: drawerView != null - ? NavigationDrawerControl( - control: drawerView.control, - children: drawerView.children, - parentDisabled: control.isDisabled, - parentAdaptive: adaptive, - backend: widget.backend) - : null, - onDrawerChanged: (opened) { - if (drawerView != null && !opened) { - widget.parent.state["drawerOpened"] = false; - dismissDrawer(drawerView.control.id); - } - }, - endDrawer: endDrawerView != null - ? NavigationDrawerControl( - control: endDrawerView.control, - children: endDrawerView.children, - parentDisabled: control.isDisabled, - parentAdaptive: adaptive, - backend: widget.backend) - : null, - onEndDrawerChanged: (opened) { - if (endDrawerView != null && !opened) { - widget.parent.state["endDrawerOpened"] = false; - dismissDrawer(endDrawerView.control.id); - } - }, - body: body, - bottomNavigationBar: bnb != null - ? createControl(control, bnb.id, control.isDisabled, - parentAdaptive: adaptive) - : null, - floatingActionButton: fab != null - ? createControl(control, fab.id, control.isDisabled, - parentAdaptive: adaptive) - : null, - floatingActionButtonLocation: fabLocation, - ); - - var systemOverlayStyle = - materialTheme.extension(); - - if (systemOverlayStyle != null && - systemOverlayStyle.systemUiOverlayStyle != null && - bar == null) { - scaffold = AnnotatedRegion( - value: systemOverlayStyle.systemUiOverlayStyle!, - child: scaffold, - ); - } - - if (bar is CupertinoAppBarControl) { - scaffold = CupertinoPageScaffold( - key: scaffoldKey, - backgroundColor: control.attrColor("bgcolor", context), - navigationBar: bar as ObstructingPreferredSizeWidget, - child: scaffold); - } - - if (widget.widgetsDesign == PageDesign.material) { - scaffold = CupertinoTheme( - data: widget.themeMode == ThemeMode.light || - ((widget.themeMode == null || - widget.themeMode == ThemeMode.system) && - widget.brightness == Brightness.light) - ? parseCupertinoTheme( - widget.parent, "theme", Brightness.light) - : widget.parent.attrString("darkTheme") != null - ? parseCupertinoTheme( - widget.parent, "darkTheme", Brightness.dark) - : parseCupertinoTheme( - widget.parent, "theme", Brightness.dark), - child: scaffold, - ); - } else if (widget.widgetsDesign == PageDesign.cupertino) { - scaffold = Theme( - data: materialTheme, - child: scaffold, - ); - } - Widget result = Directionality( - textDirection: textDirection, - child: widget.loadingPage != null - ? Stack( - children: [scaffold, widget.loadingPage!], - ) - : scaffold); - - result = PopScope( - canPop: canPop, - onPopInvokedWithResult: (didPop, result) { - if (didPop || !control.attrBool("onConfirmPop", false)!) { - return; - } - debugPrint("Page.onPopInvokedWithResult()"); - _popCompleter = Completer(); - widget.backend - .triggerControlEvent(widget.viewId, "confirm_pop"); - _popCompleter!.future - .timeout( - const Duration(minutes: 5), - onTimeout: () => false, - ) - .then((shouldPop) { - if (context.mounted && shouldPop) { - if (widget.isRootView) { - SystemNavigator.pop(); - } else { - Navigator.pop(context); - } - } - }); - }, - child: result); - - return withPageArgs((context, pageArgs) { - var backgroundDecoration = parseBoxDecoration( - Theme.of(context), control, "decoration", pageArgs); - var foregroundDecoration = parseBoxDecoration( - Theme.of(context), control, "foregroundDecoration", pageArgs); - if (backgroundDecoration != null || - foregroundDecoration != null) { - return Container( - decoration: backgroundDecoration, - foregroundDecoration: foregroundDecoration, - child: result, - ); - } - return result; - }); - }); }); } } diff --git a/packages/flet/lib/src/controls/pagelet.dart b/packages/flet/lib/src/controls/pagelet.dart index 52ca31553..d9bd3c14a 100644 --- a/packages/flet/lib/src/controls/pagelet.dart +++ b/packages/flet/lib/src/controls/pagelet.dart @@ -1,268 +1,164 @@ -import 'package:collection/collection.dart'; +import 'package:flet/src/utils/buttons.dart'; +import 'package:flet/src/utils/colors.dart'; +import 'package:flet/src/utils/numbers.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_redux/flutter_redux.dart'; -import '../flet_control_backend.dart'; -import '../models/app_state.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import '../models/controls_view_model.dart'; -import '../utils/buttons.dart'; +import '../models/page_design.dart'; +import '../utils/platform.dart'; +import '../widgets/error.dart'; import 'app_bar.dart'; -import 'create_control.dart'; +import 'base_controls.dart'; import 'cupertino_app_bar.dart'; -import 'error.dart'; -import 'flet_store_mixin.dart'; import 'navigation_drawer.dart'; -import 'page.dart'; class PageletControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const PageletControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + PageletControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _PageletControlState(); } -class _PageletControlState extends State with FletStoreMixin { +class _PageletControlState extends State { final scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) { debugPrint("Pagelet build: ${widget.control.id}"); - var appBarCtrls = - widget.children.where((c) => c.name == "appbar" && c.isVisible); - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); - var navigationBarCtrls = - widget.children.where((c) => c.name == "navigationbar" && c.isVisible); - var bottomAppBarCtrls = - widget.children.where((c) => c.name == "bottomappbar" && c.isVisible); - var bottomSheetCtrls = - widget.children.where((c) => c.name == "bottomsheet" && c.isVisible); - var drawerCtrls = - widget.children.where((c) => c.name == "drawer" && c.isVisible); - var endDrawerCtrls = - widget.children.where((c) => c.name == "enddrawer" && c.isVisible); - var fabCtrls = widget.children - .where((c) => c.name == "floatingactionbutton" && c.isVisible); + var appBar = widget.control.child("appbar"); + var content = widget.control.buildWidget("content"); + var navigationBar = widget.control.buildWidget("navigation_bar"); + var bottomAppBar = widget.control.buildWidget("bottom_appbar"); + var bottomSheet = widget.control.buildWidget("bottom_sheet"); + var drawer = widget.control.child("drawer"); + var endDrawer = widget.control.child("end_drawer"); + var fab = widget.control.buildWidget("floating_action_button"); - if (contentCtrls.isEmpty) { + if (content == null) { return const ErrorControl("Pagelet.content must be provided and visible"); } - return withPagePlatform((context, platform) { - bool disabled = widget.control.isDisabled || widget.parentDisabled; - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; + var widgetsDesign = widget.control.adaptive == true && isApplePlatform() + ? PageDesign.cupertino + : PageDesign.material; - var widgetsDesign = adaptive == true && - (platform == TargetPlatform.iOS || - platform == TargetPlatform.macOS) - ? PageDesign.cupertino - : PageDesign.material; + var bnb = navigationBar ?? bottomAppBar; - List childIds = [ - appBarCtrls.firstOrNull?.id, - drawerCtrls.firstOrNull?.id, - endDrawerCtrls.firstOrNull?.id - ].nonNulls.toList(); + final bool? drawerOpened = widget.control.getBool("drawer_opened"); + final bool? endDrawerOpened = widget.control.getBool("end_drawer_opened"); + final fabLocation = widget.control.getFloatingActionButtonLocation( + "floating_action_button_location", + FloatingActionButtonLocation.endFloat); - return StoreConnector( - distinct: true, - converter: (store) => ControlsViewModel.fromStore(store, childIds), - ignoreChange: (state) { - //debugPrint("ignoreChange: $id"); - for (var id in childIds) { - if (state.controls[id] == null) { - return true; - } - } - return false; - }, - builder: (context, childrenViews) { - var navBar = navigationBarCtrls.isNotEmpty - ? createControl( - widget.control, navigationBarCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null; - var bottomAppBar = bottomAppBarCtrls.isNotEmpty - ? createControl( - widget.control, bottomAppBarCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null; - var bnb = navBar ?? bottomAppBar; - - var appBarView = childrenViews.controlViews.firstWhereOrNull( - (v) => v.control.id == (appBarCtrls.firstOrNull?.id ?? "")); - - var drawerView = childrenViews.controlViews.firstWhereOrNull( - (v) => v.control.id == (drawerCtrls.firstOrNull?.id ?? "")); - var endDrawerView = childrenViews.controlViews.firstWhereOrNull( - (v) => v.control.id == (endDrawerCtrls.firstOrNull?.id ?? "")); - - final bool? drawerOpened = widget.control.state["drawerOpened"]; - final bool? endDrawerOpened = - widget.control.state["endDrawerOpened"]; - - final fabLocation = parseFloatingActionButtonLocation( - widget.control, - "floatingActionButtonLocation", - FloatingActionButtonLocation.endFloat); - - void dismissDrawer(String id) { - widget.backend.updateControlState(id, {"open": "false"}); - widget.backend.triggerControlEvent(id, "dismiss"); - } - - WidgetsBinding.instance.addPostFrameCallback((_) { - if (drawerView != null) { - if (scaffoldKey.currentState?.isDrawerOpen == false && - drawerOpened == true) { - widget.control.state["drawerOpened"] = false; - dismissDrawer(drawerView.control.id); - } - if (drawerView.control.attrBool("open", false)! && - drawerOpened != true) { - if (scaffoldKey.currentState?.isEndDrawerOpen == true) { - scaffoldKey.currentState?.closeEndDrawer(); - } - Future.delayed(const Duration(milliseconds: 1)).then((value) { - scaffoldKey.currentState?.openDrawer(); - widget.control.state["drawerOpened"] = true; - }); - } else if (!drawerView.control.attrBool("open", false)! && - drawerOpened == true) { - scaffoldKey.currentState?.closeDrawer(); - widget.control.state["drawerOpened"] = false; - } - } - if (endDrawerView != null) { - if (scaffoldKey.currentState?.isEndDrawerOpen == false && - endDrawerOpened == true) { - widget.control.state["endDrawerOpened"] = false; - dismissDrawer(endDrawerView.control.id); - } - if (endDrawerView.control.attrBool("open", false)! && - endDrawerOpened != true) { - if (scaffoldKey.currentState?.isDrawerOpen == true) { - scaffoldKey.currentState?.closeDrawer(); - } - Future.delayed(const Duration(milliseconds: 1)).then((value) { - scaffoldKey.currentState?.openEndDrawer(); - widget.control.state["endDrawerOpened"] = true; - }); - } else if (!endDrawerView.control.attrBool("open", false)! && - endDrawerOpened == true) { - scaffoldKey.currentState?.closeEndDrawer(); - widget.control.state["endDrawerOpened"] = false; - } - } - }); - - var bar = appBarView != null - ? appBarView.control.type == "appbar" - ? widgetsDesign == PageDesign.cupertino - ? CupertinoAppBarControl( - parent: widget.control, - control: appBarView.control, - children: appBarView.children, - parentDisabled: widget.control.isDisabled, - parentAdaptive: adaptive) - : AppBarControl( - parent: widget.control, - control: appBarView.control, - children: appBarView.children, - parentDisabled: widget.control.isDisabled, - parentAdaptive: adaptive, - height: appBarView.control - .attrDouble("toolbarHeight", kToolbarHeight)!) - : appBarView.control.type == "cupertinoappbar" - ? CupertinoAppBarControl( - parent: widget.control, - control: appBarView.control, - children: appBarView.children, - parentDisabled: widget.control.isDisabled, - parentAdaptive: adaptive, - ) as ObstructingPreferredSizeWidget - : null - : null; - - Widget scaffold = Scaffold( - key: bar == null || bar is AppBarControl ? scaffoldKey : null, - backgroundColor: widget.control.attrColor("bgcolor", context) ?? - CupertinoTheme.of(context).scaffoldBackgroundColor, - appBar: bar is AppBarControl ? bar : null, - drawer: drawerView != null - ? NavigationDrawerControl( - control: drawerView.control, - children: drawerView.children, - parentDisabled: widget.control.isDisabled, - parentAdaptive: adaptive, - backend: widget.backend) - : null, - onDrawerChanged: (opened) { - if (drawerView != null && !opened) { - widget.control.state["drawerOpened"] = false; - dismissDrawer(drawerView.control.id); - } - }, - endDrawer: endDrawerView != null - ? NavigationDrawerControl( - control: endDrawerView.control, - children: endDrawerView.children, - parentDisabled: widget.control.isDisabled, - parentAdaptive: adaptive, - backend: widget.backend) - : null, - onEndDrawerChanged: (opened) { - if (endDrawerView != null && !opened) { - widget.control.state["endDrawerOpened"] = false; - dismissDrawer(endDrawerView.control.id); - } - }, - body: contentCtrls.isNotEmpty - ? createControl( - widget.control, contentCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null, - bottomNavigationBar: bnb, - bottomSheet: bottomSheetCtrls.isNotEmpty - ? createControl( - widget.control, bottomSheetCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null, - floatingActionButton: fabCtrls.isNotEmpty - ? createControl(widget.control, fabCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null, - floatingActionButtonLocation: fabLocation); - - if (bar is CupertinoAppBarControl) { - scaffold = CupertinoPageScaffold( - key: scaffoldKey, - backgroundColor: widget.control.attrColor("bgcolor", context), - navigationBar: bar as ObstructingPreferredSizeWidget, - child: scaffold); - } + void dismissDrawer(dynamic id) { + // fixme: id + widget.control.updateProperties({"open": false}); + widget.control.triggerEvent("dismiss"); + } - return constrainedControl( - context, scaffold, widget.parent, widget.control); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (drawer != null) { + if (scaffoldKey.currentState?.isDrawerOpen == false && + drawerOpened == true) { + widget.control + .updateProperties({"drawer_opened": false}, python: false); + dismissDrawer(drawer.id); + } + if (drawer.getBool("open", false)! && drawerOpened != true) { + if (scaffoldKey.currentState?.isEndDrawerOpen == true) { + scaffoldKey.currentState?.closeEndDrawer(); + } + Future.delayed(const Duration(milliseconds: 1)).then((value) { + scaffoldKey.currentState?.openDrawer(); + widget.control + .updateProperties({"drawer_opened": true}, python: false); + }); + } else if (!drawer.getBool("open", false)! && drawerOpened == true) { + scaffoldKey.currentState?.closeDrawer(); + widget.control + .updateProperties({"drawer_opened": false}, python: false); + } + } + if (endDrawer != null) { + if (scaffoldKey.currentState?.isEndDrawerOpen == false && + endDrawerOpened == true) { + widget.control + .updateProperties({"end_drawer_opened": false}, python: false); + dismissDrawer(endDrawer.id); + } + if (endDrawer.getBool("open", false)! && endDrawerOpened != true) { + if (scaffoldKey.currentState?.isDrawerOpen == true) { + scaffoldKey.currentState?.closeDrawer(); + } + Future.delayed(const Duration(milliseconds: 1)).then((value) { + scaffoldKey.currentState?.openEndDrawer(); + widget.control + .updateProperties({"end_drawer_opened": true}, python: false); }); + } else if (!endDrawer.getBool("open", false)! && + endDrawerOpened == true) { + scaffoldKey.currentState?.closeEndDrawer(); + widget.control + .updateProperties({"end_drawer_opened": false}, python: false); + } + } }); + + var bar = appBar != null + ? appBar.type == "AppBar" + ? widgetsDesign == PageDesign.cupertino + ? CupertinoAppBarControl(control: appBar) + : AppBarControl(control: appBar) + : appBar.type == "CupertinoAppBar" + ? CupertinoAppBarControl(control: appBar) + as ObstructingPreferredSizeWidget + : null + : null; + + Widget scaffold = Scaffold( + key: bar == null || bar is AppBarControl ? scaffoldKey : null, + backgroundColor: widget.control.getColor("bgcolor", context) ?? + CupertinoTheme.of(context).scaffoldBackgroundColor, + appBar: bar is AppBarControl ? bar : null, + drawer: + drawer != null ? NavigationDrawerControl(control: drawer) : null, + onDrawerChanged: (opened) { + if (drawer != null && !opened) { + widget.control + .updateProperties({"drawer_opened": false}, python: false); + dismissDrawer(drawer.id); + } + }, + endDrawer: endDrawer != null + ? NavigationDrawerControl(control: endDrawer) + : null, + onEndDrawerChanged: (opened) { + if (endDrawer != null && !opened) { + widget.control + .updateProperties({"end_drawer_opened": false}, python: false); + dismissDrawer(endDrawer.id); + } + }, + body: content, + bottomNavigationBar: bnb, + bottomSheet: bottomSheet, + floatingActionButton: fab, + floatingActionButtonLocation: fabLocation); + + if (bar is CupertinoAppBarControl) { + scaffold = CupertinoPageScaffold( + key: scaffoldKey, + backgroundColor: widget.control.getColor("bgcolor", context), + navigationBar: bar as ObstructingPreferredSizeWidget, + child: scaffold); + } + + return ConstrainedControl(control: widget.control, child: scaffold); } } diff --git a/packages/flet/lib/src/controls/piechart.dart b/packages/flet/lib/src/controls/piechart.dart deleted file mode 100644 index e25a3b441..000000000 --- a/packages/flet/lib/src/controls/piechart.dart +++ /dev/null @@ -1,211 +0,0 @@ -import 'dart:convert'; - -import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_redux/flutter_redux.dart'; -import 'package:redux/redux.dart'; - -import '../flet_control_backend.dart'; -import '../models/app_state.dart'; -import '../models/control.dart'; -import '../utils/animations.dart'; -import '../utils/borders.dart'; -import '../utils/text.dart'; -import 'create_control.dart'; - -const eventMap = { - "FlPointerEnterEvent": "pointerEnter", - "FlPointerExitEvent": "pointerExit", - "FlPointerHoverEvent": "pointerHover", - "FlPanCancelEvent": "panCancel", - "FlPanDownEvent": "panDown", - "FlPanEndEvent": "panEnd", - "FlPanStartEvent": "panStart", - "FlPanUpdateEvent": "panUpdate", - "FlLongPressEnd": "longPressEnd", - "FlLongPressMoveUpdate": "longPressMoveUpdate", - "FlLongPressStart": "longPressStart", - "FlTapCancelEvent": "tapCancel", - "FlTapDownEvent": "tapDown", - "FlTapUpEvent": "tapUp", -}; - -class PieChartEventData extends Equatable { - final String eventType; - final int? sectionIndex; - final Offset? localPosition; - - const PieChartEventData({required this.eventType, - required this.sectionIndex, - this.localPosition}); - - Map toJson() => { - 'type': eventType, - 'section_index': sectionIndex, - "lx": localPosition?.dx, - "ly": localPosition?.dy - }; - - @override - List get props => [eventType, sectionIndex]; -} - -class PieChartSectionViewModel extends Equatable { - final Control control; - final Control? badge; - - const PieChartSectionViewModel({required this.control, required this.badge}); - - static PieChartSectionViewModel fromStore( - Store store, Control control) { - var children = store.state.controls[control.id]!.childIds - .map((childId) => store.state.controls[childId]) - .nonNulls - .where((c) => c.isVisible); - - return PieChartSectionViewModel( - control: control, - badge: children.firstWhereOrNull((c) => c.name == "badge")); - } - - @override - List get props => [control, badge]; -} - -class PieChartViewModel extends Equatable { - final Control control; - final List sections; - - const PieChartViewModel({required this.control, required this.sections}); - - static PieChartViewModel fromStore( - Store store, Control control, List children) { - return PieChartViewModel( - control: control, - sections: children - .where((c) => c.type == "section" && c.isVisible) - .map((c) => PieChartSectionViewModel.fromStore(store, c)) - .toList()); - } - - @override - List get props => [control, sections]; -} - -class PieChartControl extends StatefulWidget { - final Control? parent; - final Control control; - final List children; - final bool parentDisabled; - final FletControlBackend backend; - - const PieChartControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.backend}); - - @override - State createState() => _PieChartControlState(); -} - -class _PieChartControlState extends State { - PieChartEventData? _eventData; - - @override - Widget build(BuildContext context) { - debugPrint("PieChart build: ${widget.control.id}"); - - var animate = parseAnimation(widget.control, "animate"); - - var result = StoreConnector( - distinct: true, - converter: (store) => - PieChartViewModel.fromStore(store, widget.control, widget.children), - builder: (context, viewModel) { - List sections = viewModel.sections - .map((g) => getSectionData(Theme.of(context), widget.control, g)) - .toList(); - - Widget chart = PieChart( - PieChartData( - centerSpaceColor: - widget.control.attrColor("centerSpaceColor", context), - centerSpaceRadius: widget.control.attrDouble("centerSpaceRadius"), - sectionsSpace: widget.control.attrDouble("sectionsSpace"), - startDegreeOffset: widget.control.attrDouble("startDegreeOffset"), - pieTouchData: PieTouchData( - enabled: true, - touchCallback: widget.control.attrBool("onChartEvent", false)! - ? (FlTouchEvent evt, PieTouchResponse? resp) { - var type = evt.toString(); - var eventData = PieChartEventData( - // grab the event type found in between '' quotes - eventType: eventMap[type.substring( - type.indexOf("'") + 1, - type.lastIndexOf("'"))] ?? - "undefined", - sectionIndex: - resp?.touchedSection?.touchedSectionIndex, - localPosition: evt.localPosition, - ); - if (eventData != _eventData) { - _eventData = eventData; - debugPrint( - "PieChart ${widget.control.id} ${eventData.eventType}"); - widget.backend.triggerControlEvent(widget.control.id, - "chart_event", json.encode(eventData)); - } - } - : null, - ), - sections: sections, - ), - swapAnimationDuration: animate != null - ? animate.duration - : const Duration(milliseconds: 150), // Optional - swapAnimationCurve: animate != null ? animate.curve : Curves.linear, - ); - - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return (constraints.maxHeight == double.infinity) - ? ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 300), - child: chart, - ) - : chart; - }); - }); - - return constrainedControl(context, result, widget.parent, widget.control); - } - - PieChartSectionData getSectionData(ThemeData theme, Control parent, - PieChartSectionViewModel sectionViewModel) { - return PieChartSectionData( - value: sectionViewModel.control.attrDouble("value"), - color: sectionViewModel.control.attrColor("color", context), - radius: sectionViewModel.control.attrDouble("radius"), - showTitle: sectionViewModel.control.attrString("title", "")! != "", - title: sectionViewModel.control.attrString("title"), - titleStyle: parseTextStyle( - Theme.of(context), sectionViewModel.control, "titleStyle"), - borderSide: - parseBorderSide(theme, sectionViewModel.control, "borderSide") ?? - BorderSide.none, - titlePositionPercentageOffset: - sectionViewModel.control.attrDouble("titlePosition"), - badgeWidget: sectionViewModel.badge != null - ? createControl(sectionViewModel.control, sectionViewModel.badge!.id, - sectionViewModel.control.isDisabled) - : null, - badgePositionPercentageOffset: - sectionViewModel.control.attrDouble("badgePosition"), - ); - } -} diff --git a/packages/flet/lib/src/controls/placeholder.dart b/packages/flet/lib/src/controls/placeholder.dart index ada287d4a..c655045a2 100644 --- a/packages/flet/lib/src/controls/placeholder.dart +++ b/packages/flet/lib/src/controls/placeholder.dart @@ -1,42 +1,27 @@ import 'package:flutter/material.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import 'create_control.dart'; +import '../utils/colors.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; class PlaceholderControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - const PlaceholderControl( - {super.key, - required this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive}); + const PlaceholderControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("Placeholder build: ${control.id}"); - var contentCtrls = children.where((c) => c.isVisible); - return baseControl( - context, - Placeholder( - fallbackHeight: control.attrDouble("fallbackHeight", 400.0)!, - fallbackWidth: control.attrDouble("fallbackWidth", 400.0)!, - color: - control.attrColor("color", context, const Color(0xFF455A64))!, - strokeWidth: control.attrDouble("strokeWidth", 2.0)!, - child: contentCtrls.isNotEmpty - ? createControl(control, contentCtrls.first.id, - control.isDisabled || parentDisabled, - parentAdaptive: control.isAdaptive ?? parentAdaptive) - : null), - parent, - control); + final placeholder = Placeholder( + fallbackHeight: control.getDouble("fallback_height", 400.0)!, + fallbackWidth: control.getDouble("fallback_width", 400.0)!, + color: control.getColor("color", context, const Color(0xFF455A64))!, + strokeWidth: control.getDouble("stroke_width", 2.0)!, + child: control.buildWidget("content")); + + return ConstrainedControl(control: control, child: placeholder); } } diff --git a/packages/flet/lib/src/controls/popup_menu_button.dart b/packages/flet/lib/src/controls/popup_menu_button.dart index 8f82b6f73..22fb61f0d 100644 --- a/packages/flet/lib/src/controls/popup_menu_button.dart +++ b/packages/flet/lib/src/controls/popup_menu_button.dart @@ -1,151 +1,113 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/animations.dart'; import '../utils/borders.dart'; import '../utils/box.dart'; import '../utils/buttons.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; -import '../utils/icons.dart'; +import '../utils/misc.dart'; import '../utils/mouse.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'flet_store_mixin.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; -class PopupMenuButtonControl extends StatelessWidget with FletStoreMixin { - final Control? parent; +class PopupMenuButtonControl extends StatelessWidget { final Control control; - final bool parentDisabled; - final List children; - final FletControlBackend backend; - const PopupMenuButtonControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.backend}); + const PopupMenuButtonControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("PopupMenuButton build: ${control.id}"); - var icon = parseIcon(control.attrString("icon")); - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - bool disabled = control.isDisabled || parentDisabled; + var content = control.buildTextOrWidget("content"); - Widget? child = contentCtrls.isNotEmpty - ? createControl(control, contentCtrls.first.id, disabled) - : null; + var popupMenuButton = PopupMenuButton( + enabled: !control.disabled, + tooltip: null, + icon: control.buildIconOrWidget("icon"), + iconSize: control.getDouble("icon_size"), + splashRadius: control.getDouble("splash_radius"), + shadowColor: control.getColor("shadow_color", context), + surfaceTintColor: control.getColor("surface_tint_color", context), + iconColor: control.getColor("icon_color", context), + elevation: control.getDouble("elevation"), + enableFeedback: control.getBool("enable_feedback"), + padding: control.getPadding("padding", const EdgeInsets.all(8))!, + color: control.getColor("bgcolor", context), + clipBehavior: control.getClipBehavior("clip_behavior", Clip.none)!, + shape: control.getShape("shape", Theme.of(context), + defaultValue: (Theme.of(context).useMaterial3 + ? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10)) + : null))!, + constraints: control.getBoxConstraints("size_constraints"), + style: control.getButtonStyle("style", Theme.of(context)), + popUpAnimationStyle: control.getAnimationStyle("popup_animation_style"), + menuPadding: control.getPadding("menu_padding"), + position: control.getPopupMenuPosition("menu_position"), + onSelected: (String selection) => + control.triggerEvent("select", selection), + onCanceled: () => control.triggerEvent("cancel"), + onOpened: () => control.triggerEvent("open"), + itemBuilder: (BuildContext context) => control + .children("items") + .where((i) => i.type == "PopupMenuItem") + .map((item) { + var checked = item.getBool("checked"); + var height = item.getDouble("height", 48.0)!; + var padding = item.getPadding("padding"); + var itemContent = item.buildTextOrWidget("content"); + var itemIcon = item.buildIconOrWidget("icon"); + var mouseCursor = item.getMouseCursor("mouse_cursor"); - var popupButton = withControls( - children - .where((c) => c.name != "content" && c.isVisible) - .map((c) => c.id), (content, viewModel) { - return PopupMenuButton( - enabled: !disabled, - tooltip: null, - icon: icon != null ? Icon(icon) : null, - iconSize: control.attrDouble("iconSize"), - splashRadius: control.attrDouble("splashRadius"), - shadowColor: control.attrColor("shadowColor", context), - surfaceTintColor: control.attrColor("surfaceTintColor", context), - iconColor: control.attrColor("iconColor", context), - elevation: control.attrDouble("elevation"), - enableFeedback: control.attrBool("enableFeedback"), - padding: - parseEdgeInsets(control, "padding", const EdgeInsets.all(8))!, - color: control.attrColor("bgcolor", context), - clipBehavior: - parseClip(control.attrString("clipBehavior"), Clip.none)!, - shape: parseOutlinedBorder(control, "shape") ?? - (Theme.of(context).useMaterial3 - ? RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10)) - : null), - constraints: parseBoxConstraints(control, "sizeConstraints"), - style: parseButtonStyle(Theme.of(context), control, "style"), - popUpAnimationStyle: - parseAnimationStyle(control, "popupAnimationStyle"), - menuPadding: parseEdgeInsets(control, "menuPadding"), - onSelected: (String selection) { - backend.triggerControlEvent(control.id, "select", selection); - }, - onCanceled: () { - backend.triggerControlEvent(control.id, "cancel"); - }, - onOpened: () { - backend.triggerControlEvent(control.id, "open"); - }, - position: parsePopupMenuPosition(control.attrString("menuPosition")), - itemBuilder: (BuildContext context) => - viewModel.controlViews.map((cv) { - var itemIcon = parseIcon(cv.control.attrString("icon")); - var text = cv.control.attrString("text", "")!; - var checked = cv.control.attrBool("checked"); - var height = cv.control.attrDouble("height", 48.0)!; - var padding = parseEdgeInsets(cv.control, "padding"); - var disabled = cv.control.isDisabled || parentDisabled; - var contentCtrls = cv.children - .where((c) => c.name == "content" && c.isVisible); + Widget? child; + if (itemContent != null && itemIcon == null) { + child = itemContent; + } else if (itemContent == null && itemIcon != null) { + child = itemIcon; + } else if (itemContent != null && itemIcon != null) { + child = Row(children: [ + itemIcon, + const SizedBox(width: 8), + itemContent + ]); + } - Widget? child; - if (contentCtrls.isNotEmpty) { - // custom content - child = createControl( - cv.control, contentCtrls.first.id, parentDisabled); - } else if (itemIcon != null && text != "") { - // icon and text - child = Row(children: [ - Icon(itemIcon), - const SizedBox(width: 8), - Text(text) - ]); - } else if (text != "") { - child = Text(text); - } + var result = checked != null + ? CheckedPopupMenuItem( + value: item.id.toString(), + checked: checked, + height: height, + padding: padding, + enabled: !item.disabled, + mouseCursor: mouseCursor, + onTap: () => item.triggerEvent("click", !checked), + child: child, + ) + : PopupMenuItem( + value: item.id.toString(), + height: height, + padding: padding, + enabled: !item.disabled, + mouseCursor: mouseCursor, + onTap: () { + item.triggerEvent("click"); + }, + child: child); - var item = checked != null - ? CheckedPopupMenuItem( - value: cv.control.id, - checked: checked, - height: height, - padding: padding, - enabled: !disabled || !cv.control.isDisabled, - mouseCursor: parseMouseCursor( - cv.control.attrString("mouseCursor")), - onTap: () { - backend.triggerControlEvent( - cv.control.id, "click", "${!checked}"); - }, - child: child, - ) - : PopupMenuItem( - value: cv.control.id, - height: height, - padding: padding, - enabled: !disabled || !cv.control.isDisabled, - mouseCursor: parseMouseCursor( - cv.control.attrString("mouseCursor")), - onTap: () { - backend.triggerControlEvent(cv.control.id, "click"); - }, - child: child); + return child != null + ? result + : const PopupMenuDivider() as PopupMenuEntry; + }).toList(), + child: content); - return child != null - ? item - : const PopupMenuDivider() as PopupMenuEntry; - }).toList(), - child: child); - }); - return constrainedControl( - context, - TooltipVisibility( - visible: control.attrString("tooltip") == null, child: popupButton), - parent, - control); + return ConstrainedControl( + control: control, + child: TooltipVisibility( + visible: control.getString("tooltip") == null, + child: popupMenuButton)); } } diff --git a/packages/flet/lib/src/controls/progress_bar.dart b/packages/flet/lib/src/controls/progress_bar.dart index 7188fbf01..7efbfc5ce 100644 --- a/packages/flet/lib/src/controls/progress_bar.dart +++ b/packages/flet/lib/src/controls/progress_bar.dart @@ -2,36 +2,36 @@ import 'package:flutter/material.dart'; import '../models/control.dart'; import '../utils/borders.dart'; -import 'create_control.dart'; +import '../utils/colors.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; class ProgressBarControl extends StatelessWidget { - final Control? parent; final Control control; - const ProgressBarControl( - {super.key, required this.parent, required this.control}); + const ProgressBarControl({ + super.key, + required this.control, + }); @override Widget build(BuildContext context) { debugPrint("ProgressBar build: ${control.id}"); - - return constrainedControl( - context, - LinearProgressIndicator( - value: control.attrDouble("value"), - minHeight: control.attrDouble("minHeight", 4)!, - color: control.attrColor("color", context), - backgroundColor: control.attrColor("bgColor", context), - semanticsLabel: control.attrString("semanticsLabel"), - semanticsValue: control.attrDouble("semanticsValue")?.toString(), - borderRadius: - parseBorderRadius(control, "borderRadius", BorderRadius.zero)!, - stopIndicatorColor: control.attrColor("stopIndicatorColor", context), - stopIndicatorRadius: control.attrDouble("stopIndicatorRadius"), - trackGap: control.attrDouble("trackGap"), - year2023: control.attrBool("year2023"), - ), - parent, - control); + final indicator = LinearProgressIndicator( + value: control.getDouble("value"), + minHeight: control.getDouble("bar_height", 4)!, + color: control.getColor("color", context), + backgroundColor: control.getColor("bgcolor", context), + semanticsLabel: control.getString("semantics_label"), + semanticsValue: control.getDouble("semantics_value")?.toString(), + borderRadius: + control.getBorderRadius("border_radius", BorderRadius.zero)!, + stopIndicatorColor: control.getColor("stop_indicator_color", context), + stopIndicatorRadius: control.getDouble("stop_indicator_radius"), + trackGap: control.getDouble("track_gap"), + year2023: control.getBool( + "year_2023"), // todo: deprecated and to be removed in future versions + ); + return ConstrainedControl(control: control, child: indicator); } } diff --git a/packages/flet/lib/src/controls/progress_ring.dart b/packages/flet/lib/src/controls/progress_ring.dart index b7da78e0f..6b2b759c2 100644 --- a/packages/flet/lib/src/controls/progress_ring.dart +++ b/packages/flet/lib/src/controls/progress_ring.dart @@ -2,39 +2,38 @@ import 'package:flutter/material.dart'; import '../models/control.dart'; import '../utils/box.dart'; +import '../utils/colors.dart'; import '../utils/edge_insets.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; class ProgressRingControl extends StatelessWidget { - final Control? parent; final Control control; - const ProgressRingControl( - {super.key, required this.parent, required this.control}); + const ProgressRingControl({ + super.key, + required this.control, + }); @override Widget build(BuildContext context) { debugPrint("ProgressRing build: ${control.id}"); - - return constrainedControl( - context, - CircularProgressIndicator( - value: control.attrDouble("value"), - strokeWidth: control.attrDouble("strokeWidth", 4)!, - color: control.attrColor("color", context), - backgroundColor: control.attrColor("bgColor", context), - semanticsLabel: control.attrString("semanticsLabel"), - strokeCap: parseStrokeCap(control.attrString("strokeCap")), - semanticsValue: control.attrDouble("semanticsValue")?.toString(), - strokeAlign: control.attrDouble("strokeAlign", 0)!, - trackGap: control.attrDouble("trackGap"), - constraints: parseBoxConstraints(control, "sizeConstraints"), - padding: parseEdgeInsets(control, "padding"), - // TODO: deprecated in v0.27.0, and will be removed in future versions - year2023: control.attrBool("year2023"), - ), - parent, - control); + final indicator = CircularProgressIndicator( + value: control.getDouble("value"), + strokeWidth: control.getDouble("stroke_width", 4)!, + color: control.getColor("color", context), + backgroundColor: control.getColor("bgcolor", context), + semanticsLabel: control.getString("semantics_label"), + strokeCap: control.getStrokeCap("stroke_cap"), + semanticsValue: control.getDouble("semantics_value")?.toString(), + strokeAlign: control.getDouble("stroke_align", 0)!, + trackGap: control.getDouble("track_gap"), + constraints: control.getBoxConstraints("size_constraints"), + padding: control.getPadding("padding"), + year2023: control.getBool( + "year2023"), // todo: deprecated and to be removed in future versions + ); + return ConstrainedControl(control: control, child: indicator); } } diff --git a/packages/flet/lib/src/controls/radio.dart b/packages/flet/lib/src/controls/radio.dart index ae5291843..25b540d1d 100644 --- a/packages/flet/lib/src/controls/radio.dart +++ b/packages/flet/lib/src/controls/radio.dart @@ -1,39 +1,28 @@ -import 'package:flet/src/utils/theme.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; import '../utils/colors.dart'; +import '../utils/misc.dart'; import '../utils/mouse.dart'; -import '../utils/others.dart'; +import '../utils/numbers.dart'; import '../utils/text.dart'; -import 'create_control.dart'; -import 'cupertino_radio.dart'; -import 'error.dart'; -import 'flet_store_mixin.dart'; +import '../utils/theme.dart'; +import '../widgets/error.dart'; +import '../widgets/radio_group_provider.dart'; +import 'base_controls.dart'; import 'list_tile.dart'; - class RadioControl extends StatefulWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - - const RadioControl( - {super.key, - this.parent, - required this.control, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + + RadioControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _RadioControlState(); } -class _RadioControlState extends State with FletStoreMixin { +class _RadioControlState extends State { late final FocusNode _focusNode; @override @@ -44,8 +33,7 @@ class _RadioControlState extends State with FletStoreMixin { } void _onFocusChange() { - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); } @override @@ -55,103 +43,79 @@ class _RadioControlState extends State with FletStoreMixin { super.dispose(); } - void _onChange(String ancestorId, String? value) { - var svalue = value ?? ""; - debugPrint(svalue); - widget.backend.updateControlState(ancestorId, {"value": svalue}); - widget.backend.triggerControlEvent(ancestorId, "change", svalue); + void _onChange(Control radioGroup, String? value) { + radioGroup.updateProperties({"value": value}, notify: true); + radioGroup.triggerEvent("change", value); } @override Widget build(BuildContext context) { debugPrint("Radio build: ${widget.control.id}"); - return withPagePlatform((context, platform) { - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - if (adaptive == true && - (platform == TargetPlatform.iOS || - platform == TargetPlatform.macOS)) { - return CupertinoRadioControl( - control: widget.control, - parentDisabled: widget.parentDisabled, - backend: widget.backend); - } - - String label = widget.control.attrString("label", "")!; - String value = widget.control.attrString("value", "")!; - LabelPosition labelPosition = parseLabelPosition( - widget.control.attrString("labelPosition"), LabelPosition.right)!; - VisualDensity? visualDensity = - parseVisualDensity(widget.control.attrString("visualDensity")); - bool autofocus = widget.control.attrBool("autofocus", false)!; - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - TextStyle? labelStyle = - parseTextStyle(Theme.of(context), widget.control, "labelStyle"); - if (disabled && labelStyle != null) { - labelStyle = labelStyle.apply(color: Theme.of(context).disabledColor); - } - - return withControlAncestor(widget.control.id, "radiogroup", - (context, viewModel) { - debugPrint("Radio StoreConnector build: ${widget.control.id}"); - - if (viewModel.ancestor == null) { - return const ErrorControl("Radio must be enclosed within RadioGroup"); - } - - String groupValue = viewModel.ancestor!.attrString("value", "")!; - String ancestorId = viewModel.ancestor!.id; - - var radio = Radio( - autofocus: autofocus, - focusNode: _focusNode, - groupValue: groupValue, - mouseCursor: parseMouseCursor(widget.control.attrString("mouseCursor")), - value: value, - activeColor: widget.control.attrColor("activeColor", context), - focusColor: widget.control.attrColor("focusColor", context), - hoverColor: widget.control.attrColor("hoverColor", context), - splashRadius: widget.control.attrDouble("splashRadius"), - toggleable: widget.control.attrBool("toggleable", false)!, - fillColor: parseWidgetStateColor( - Theme.of(context), widget.control, "fillColor"), - overlayColor: parseWidgetStateColor( - Theme.of(context), widget.control, "overlayColor"), - visualDensity: visualDensity, - onChanged: !disabled - ? (String? value) { - _onChange(ancestorId, value); - } - : null); - - ListTileClicks.of(context)?.notifier.addListener(() { - _onChange(ancestorId, value); - }); - - Widget result = radio; - if (label != "") { - var labelWidget = disabled - ? Text(label, style: labelStyle) - : MouseRegion( - cursor: SystemMouseCursors.click, - child: Text(label, style: labelStyle)); - result = MergeSemantics( - child: GestureDetector( - onTap: !disabled - ? () { - _onChange(ancestorId, value); - } - : null, - child: labelPosition == LabelPosition.right - ? Row(children: [radio, labelWidget]) - : Row(children: [labelWidget, radio]))); - } - - return constrainedControl( - context, result, widget.parent, widget.control); - }); + var label = widget.control.getString("label", "")!; + var value = widget.control.getString("value", "")!; + var labelPosition = + widget.control.getLabelPosition("label_position", LabelPosition.right)!; + var visualDensity = widget.control.getVisualDensity("visual_density"); + bool autofocus = widget.control.getBool("autofocus", false)!; + + var labelStyle = + widget.control.getTextStyle("label_style", Theme.of(context)); + if (widget.control.disabled && labelStyle != null) { + labelStyle = labelStyle.apply(color: Theme.of(context).disabledColor); + } + + debugPrint("Radio StoreConnector build: ${widget.control.id}"); + + var radioGroup = RadioGroupProvider.of(context); + + if (radioGroup == null) { + return const ErrorControl("Radio must be enclosed within RadioGroup"); + } + + String groupValue = radioGroup.getString("value", "")!; + + var radio = Radio( + autofocus: autofocus, + focusNode: _focusNode, + groupValue: groupValue, + mouseCursor: parseMouseCursor(widget.control.getString("mouseCursor")), + value: value, + activeColor: widget.control.getColor("active_color", context), + focusColor: widget.control.getColor("focus_color", context), + hoverColor: widget.control.getColor("hover_color", context), + splashRadius: widget.control.getDouble("splash_radius"), + toggleable: widget.control.getBool("toggleable", false)!, + fillColor: + widget.control.getWidgetStateColor("fill_color", Theme.of(context)), + overlayColor: widget.control + .getWidgetStateColor("overlay_color", Theme.of(context)), + visualDensity: visualDensity, + onChanged: !widget.control.disabled + ? (String? value) => _onChange(radioGroup, value) + : null); + + ListTileClicks.of(context)?.notifier.addListener(() { + _onChange(radioGroup, value); }); + + Widget result = radio; + if (label != "") { + var labelWidget = widget.control.disabled + ? Text(label, style: labelStyle) + : MouseRegion( + cursor: SystemMouseCursors.click, + child: Text(label, style: labelStyle)); + result = MergeSemantics( + child: GestureDetector( + onTap: !widget.control.disabled + ? () => _onChange(radioGroup, value) + : null, + child: labelPosition == LabelPosition.right + ? Row(children: [radio, labelWidget]) + : Row(children: [labelWidget, radio]))); + } + + return ConstrainedControl(control: widget.control, child: result); } } diff --git a/packages/flet/lib/src/controls/radio_group.dart b/packages/flet/lib/src/controls/radio_group.dart index 41c0cef6f..3d7e124d9 100644 --- a/packages/flet/lib/src/controls/radio_group.dart +++ b/packages/flet/lib/src/controls/radio_group.dart @@ -1,38 +1,23 @@ import 'package:flutter/material.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../widgets/error.dart'; +import '../widgets/radio_group_provider.dart'; class RadioGroupControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - const RadioGroupControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive}); + const RadioGroupControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("RadioGroupControl build: ${control.id}"); - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - bool disabled = control.isDisabled || parentDisabled; - - if (contentCtrls.isEmpty) { - return const ErrorControl( - "RadioGroup.content must be provided and visible"); - } - - return createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: parentAdaptive); + return RadioGroupProvider( + radioGroupControl: control, + child: control.buildWidget("content") ?? + const ErrorControl( + "RadioGroup.content must be provided and visible")); } } diff --git a/packages/flet/lib/src/controls/range_slider.dart b/packages/flet/lib/src/controls/range_slider.dart index 2d7a4de38..3f57443ae 100644 --- a/packages/flet/lib/src/controls/range_slider.dart +++ b/packages/flet/lib/src/controls/range_slider.dart @@ -1,25 +1,18 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; import '../utils/colors.dart'; import '../utils/debouncer.dart'; import '../utils/mouse.dart'; +import '../utils/numbers.dart'; import '../utils/platform.dart'; -import 'create_control.dart'; +import 'base_controls.dart'; class RangeSliderControl extends StatefulWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final FletControlBackend backend; - const RangeSliderControl( - {super.key, - this.parent, - required this.control, - required this.parentDisabled, - required this.backend}); + RangeSliderControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _SliderControlState(); @@ -40,14 +33,11 @@ class _SliderControlState extends State { } void onChange(double startValue, double endValue) { - var props = { - "startvalue": startValue.toString(), - "endvalue": endValue.toString() - }; - widget.backend.updateControlState(widget.control.id, props, server: false); + var props = {"start_value": startValue, "end_value": endValue}; + widget.control.updateProperties(props, python: false, notify: true); _debouncer.run(() { - widget.backend.updateControlState(widget.control.id, props); - widget.backend.triggerControlEvent(widget.control.id, "change"); + widget.control.updateProperties(props, notify: true); + widget.control.triggerEvent("change"); }); } @@ -55,15 +45,14 @@ class _SliderControlState extends State { Widget build(BuildContext context) { debugPrint("RangeSliderControl build: ${widget.control.id}"); - double startValue = widget.control.attrDouble("startValue", 0)!; - double endValue = widget.control.attrDouble("endValue", 0)!; - String label = widget.control.attrString("label", "")!; - bool disabled = widget.control.isDisabled || widget.parentDisabled; + double startValue = widget.control.getDouble("start_value", 0)!; + double endValue = widget.control.getDouble("end_value", 0)!; + String label = widget.control.getString("label", "")!; - double min = widget.control.attrDouble("min", 0)!; - double max = widget.control.attrDouble("max", 1)!; + double min = widget.control.getDouble("min", 0)!; + double max = widget.control.getDouble("max", 1)!; - int round = widget.control.attrInt("round", 0)!; + int round = widget.control.getInt("round", 0)!; debugPrint("SliderControl build: ${widget.control.id}"); @@ -74,31 +63,28 @@ class _SliderControlState extends State { (label).replaceAll("{value}", endValue.toStringAsFixed(round))), min: min, max: max, - divisions: widget.control.attrInt("divisions"), - activeColor: widget.control.attrColor("activeColor", context), - inactiveColor: widget.control.attrColor("inactiveColor", context), - mouseCursor: parseWidgetStateMouseCursor(widget.control, "mouseCursor"), - overlayColor: parseWidgetStateColor( - Theme.of(context), widget.control, "overlayColor"), - onChanged: !disabled + divisions: widget.control.getInt("divisions"), + activeColor: widget.control.getColor("active_color", context), + inactiveColor: widget.control.getColor("inactive_color", context), + mouseCursor: widget.control.getWidgetStateMouseCursor("mouse_cursor"), + overlayColor: widget.control + .getWidgetStateColor("overlay_color", Theme.of(context)), + onChanged: !widget.control.disabled ? (RangeValues newValues) { onChange(newValues.start, newValues.end); } : null, - onChangeStart: !disabled + onChangeStart: !widget.control.disabled ? (RangeValues newValues) { - widget.backend - .triggerControlEvent(widget.control.id, "change_start"); + widget.control.triggerEvent("change_start"); } : null, - onChangeEnd: !disabled + onChangeEnd: !widget.control.disabled ? (RangeValues newValues) { - widget.backend - .triggerControlEvent(widget.control.id, "change_end"); + widget.control.triggerEvent("change_end"); } : null); - return constrainedControl( - context, rangeSlider, widget.parent, widget.control); + return ConstrainedControl(control: widget.control, child: rangeSlider); } } diff --git a/packages/flet/lib/src/controls/reorderable_draggable.dart b/packages/flet/lib/src/controls/reorderable_draggable.dart index c55562f0e..679543f75 100644 --- a/packages/flet/lib/src/controls/reorderable_draggable.dart +++ b/packages/flet/lib/src/controls/reorderable_draggable.dart @@ -1,49 +1,33 @@ import 'package:flutter/material.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; -class ReorderableDraggableControl extends StatefulWidget { - final Control? parent; +class ReorderableDraggableControl extends StatelessWidget { final Control control; - final bool parentDisabled; - final List children; - final bool? parentAdaptive; - const ReorderableDraggableControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive}); + const ReorderableDraggableControl({super.key, required this.control}); - @override - State createState() => _ListViewControlState(); -} - -class _ListViewControlState extends State { @override Widget build(BuildContext context) { - debugPrint("ReorderableDraggableControl build: ${widget.control.id}"); - - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; + debugPrint("ReorderableDraggableControl build: ${control.id}"); - var index = widget.control.attrInt("index"); + var index = control.getInt("index"); if (index == null) { return const ErrorControl("ReorderableDraggable.index is invalid"); } - var content = widget.children.where((c) => c.isVisible).firstOrNull; + var content = control.buildWidget("content"); if (content == null) { return const ErrorControl( "ReorderableDraggable.content must be set and visible"); } return ReorderableDragStartListener( - index: index, - child: createControl(widget.control, content.id, widget.parentDisabled, - parentAdaptive: adaptive)); + index: index, + enabled: !control.disabled, + child: content, + ); } } diff --git a/packages/flet/lib/src/controls/reorderable_list_view.dart b/packages/flet/lib/src/controls/reorderable_list_view.dart index 750bd1cec..550f15e95 100644 --- a/packages/flet/lib/src/controls/reorderable_list_view.dart +++ b/packages/flet/lib/src/controls/reorderable_list_view.dart @@ -1,32 +1,21 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/edge_insets.dart'; +import '../utils/misc.dart'; import '../utils/mouse.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; +import 'control_widget.dart'; import 'scroll_notification_control.dart'; import 'scrollable_control.dart'; class ReorderableListViewControl extends StatefulWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final List children; - final bool? parentAdaptive; - final FletControlBackend backend; - - const ReorderableListViewControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + + ReorderableListViewControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _ListViewControlState(); @@ -34,11 +23,19 @@ class ReorderableListViewControl extends StatefulWidget { class _ListViewControlState extends State { late final ScrollController _controller; + List _controls = []; @override void initState() { super.initState(); _controller = ScrollController(); + _controls = [...widget.control.children("controls")]; + } + + @override + void didUpdateWidget(covariant ReorderableListViewControl oldWidget) { + _controls = [...widget.control.children("controls")]; + super.didUpdateWidget(oldWidget); } @override @@ -51,73 +48,53 @@ class _ListViewControlState extends State { Widget build(BuildContext context) { debugPrint("ReorderableDraggableControl build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - - var horizontal = widget.control.attrBool("horizontal", false)!; + var horizontal = widget.control.getBool("horizontal", false)!; var buildControlsOnDemand = - widget.control.attrBool("buildControlsOnDemand", true)!; - var itemExtent = widget.control.attrDouble("itemExtent"); - var cacheExtent = widget.control.attrDouble("cacheExtent"); + widget.control.getBool("build_controls_on_demand", true)!; + var itemExtent = widget.control.getDouble("item_extent"); + var cacheExtent = widget.control.getDouble("cache_extent"); var firstItemPrototype = - widget.control.attrBool("firstItemPrototype", false)!; - var padding = parseEdgeInsets(widget.control, "padding"); - var reverse = widget.control.attrBool("reverse", false)!; + widget.control.getBool("first_item_prototype", false)!; + var padding = widget.control.getPadding("padding"); + var reverse = widget.control.getBool("reverse", false)!; var showDefaultDragHandles = - widget.control.attrBool("showDefaultDragHandles", true)!; - var anchor = widget.control.attrDouble("anchor", 0.0)!; - var mouseCursor = - parseMouseCursor(widget.control.attrString("mouseCursor")); + widget.control.getBool("show_default_drag_handles", true)!; + var anchor = widget.control.getDouble("anchor", 0.0)!; var clipBehavior = - parseClip(widget.control.attrString("clipBehavior"), Clip.hardEdge)!; - List ctrls = widget.children - .where((c) => c.name != "header" && c.name != "footer" && c.isVisible) + widget.control.getClipBehavior("clip_behavior", Clip.hardEdge)!; + var controls = _controls + .map((child) => ControlWidget(key: ValueKey(child.id), control: child)) .toList(); var scrollDirection = horizontal ? Axis.horizontal : Axis.vertical; - var headerCtrls = - widget.children.where((c) => c.name == "header" && c.isVisible); - var header = headerCtrls.isNotEmpty - ? createControl(widget.control, headerCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null; - var footerCtrls = - widget.children.where((c) => c.name == "footer" && c.isVisible); - var footer = footerCtrls.isNotEmpty - ? createControl(widget.control, footerCtrls.first.id, disabled, - parentAdaptive: adaptive) - : null; - var prototypeItem = firstItemPrototype && widget.children.isNotEmpty - ? createControl(widget.control, ctrls[0].id, disabled, - parentAdaptive: adaptive) - : null; + var header = widget.control.buildWidget("header"); + var footer = widget.control.buildWidget("footer"); + var prototypeItem = + firstItemPrototype && controls.isNotEmpty ? controls[0] : null; var autoScrollerVelocityScalar = - widget.control.attrDouble("autoScrollerVelocityScalar"); + widget.control.getDouble("auto_scroller_velocity_scalar"); + var mouseCursor = widget.control.getMouseCursor("mouse_cursor"); void onReorder(int oldIndex, int newIndex) { - debugPrint("onReorder: $oldIndex -> $newIndex"); - if (newIndex > oldIndex) { - newIndex -= 1; - } setState(() { - final Control movedControl = widget.children.removeAt(oldIndex); - widget.children.insert(newIndex, movedControl); + if (oldIndex < newIndex) { + newIndex -= 1; + } + final item = _controls.removeAt(oldIndex); + _controls.insert(newIndex, item); }); - widget.backend.triggerControlEvent(widget.control.id, "reorder", - jsonEncode({"old": oldIndex, "new": newIndex})); + widget.control.triggerEvent( + "reorder", {"old_index": oldIndex, "new_index": newIndex}); } void onReorderEnd(int newIndex) { - widget.backend.triggerControlEvent( - widget.control.id, "reorder_end", jsonEncode({"new": newIndex})); + widget.control.triggerEvent("reorder_end", {"new_index": newIndex}); } void onReorderStart(int oldIndex) { - widget.backend.triggerControlEvent( - widget.control.id, "reorder_start", jsonEncode({"old": oldIndex})); + widget.control.triggerEvent("reorder_start", {"old_index": oldIndex}); } - Widget listView = LayoutBuilder( + Widget result = LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { var shrinkWrap = (!horizontal && constraints.maxHeight == double.infinity) || @@ -133,7 +110,7 @@ class _ListViewControlState extends State { scrollDirection: scrollDirection, shrinkWrap: shrinkWrap, padding: padding, - itemCount: ctrls.length, + itemCount: controls.length, itemExtent: itemExtent, mouseCursor: mouseCursor, anchor: anchor, @@ -145,9 +122,7 @@ class _ListViewControlState extends State { onReorderEnd: onReorderEnd, onReorderStart: onReorderStart, itemBuilder: (context, index) { - return createControl( - widget.control, ctrls[index].id, disabled, - parentAdaptive: adaptive); + return controls[index]; }, ) : ReorderableListView( @@ -169,29 +144,24 @@ class _ListViewControlState extends State { onReorder: onReorder, onReorderEnd: onReorderEnd, onReorderStart: onReorderStart, - children: ctrls.map((c) { - return createControl(widget.control, c.id, disabled, - parentAdaptive: adaptive); - }).toList(), + children: controls, ); child = ScrollableControl( control: widget.control, scrollDirection: scrollDirection, scrollController: _controller, - backend: widget.backend, - parentAdaptive: adaptive, child: child); - if (widget.control.attrBool("onScroll", false)!) { - child = ScrollNotificationControl( - control: widget.control, backend: widget.backend, child: child); + if (widget.control.getBool("on_scroll", false)!) { + child = + ScrollNotificationControl(control: widget.control, child: child); } return child; }, ); - return constrainedControl(context, listView, widget.parent, widget.control); + return ConstrainedControl(control: widget.control, child: result); } } diff --git a/packages/flet/lib/src/controls/responsive_row.dart b/packages/flet/lib/src/controls/responsive_row.dart index 9f7840573..4fa77a362 100644 --- a/packages/flet/lib/src/controls/responsive_row.dart +++ b/packages/flet/lib/src/controls/responsive_row.dart @@ -1,59 +1,50 @@ import 'package:flutter/widgets.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; import '../utils/alignment.dart'; +import '../utils/numbers.dart'; import '../utils/responsive.dart'; -import 'create_control.dart'; -import 'error.dart'; -import 'flet_store_mixin.dart'; +import '../widgets/error.dart'; +import '../widgets/flet_store_mixin.dart'; +import 'base_controls.dart'; +import 'control_widget.dart'; class ResponsiveRowControl extends StatelessWidget with FletStoreMixin { - final Control? parent; final Control control; - final bool parentDisabled; - final bool? parentAdaptive; - final List children; - final FletControlBackend backend; - const ResponsiveRowControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const ResponsiveRowControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("ResponsiveRowControl build: ${control.id}"); - final columns = parseResponsiveNumber(control, "columns", 12); - final spacing = parseResponsiveNumber(control, "spacing", 10); - final runSpacing = parseResponsiveNumber(control, "runSpacing", 10); - bool disabled = control.isDisabled || parentDisabled; - bool? adaptive = control.attrBool("adaptive") ?? parentAdaptive; + final columns = control.getResponsiveNumber("columns", 12)!; + final spacing = control.getResponsiveNumber("spacing", 10)!; + final runSpacing = control.getResponsiveNumber("run_spacing", 10)!; + return withPageSize((context, view) { - var w = LayoutBuilder( + var result = LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - debugPrint( - "ResponsiveRow constraints.maxWidth: ${constraints.maxWidth}"); - debugPrint( - "ResponsiveRow constraints.maxHeight: ${constraints.maxHeight}"); - + // breakpoints + final rawBreakpoints = + control.get("breakpoints", view.breakpoints)!; + final breakpoints = {}; + rawBreakpoints.forEach((k, v) { + final val = parseDouble(v); + if (val != null) { + breakpoints[k.toString()] = val; + } + }); var bpSpacing = - getBreakpointNumber(spacing, view.size.width, view.breakpoints); - + getBreakpointNumber(spacing, view.size.width, breakpoints); var bpColumns = - getBreakpointNumber(columns, view.size.width, view.breakpoints); + getBreakpointNumber(columns, view.size.width, breakpoints); double totalCols = 0; List controls = []; - for (var ctrl in children.where((c) => c.isVisible)) { - final col = parseResponsiveNumber(ctrl, "col", 12); - var bpCol = - getBreakpointNumber(col, view.size.width, view.breakpoints); + for (var ctrl in control.children("controls")) { + final col = ctrl.getResponsiveNumber("col", 12)!; + var bpCol = getBreakpointNumber(col, view.size.width, breakpoints); totalCols += bpCol; // calculate child width @@ -62,12 +53,9 @@ class ResponsiveRowControl extends StatelessWidget with FletStoreMixin { var childWidth = colWidth * bpCol + bpSpacing * (bpCol - 1); controls.add(ConstrainedBox( - constraints: BoxConstraints( - minWidth: childWidth, - maxWidth: childWidth, - ), - child: createControl(control, ctrl.id, disabled, - parentAdaptive: adaptive), + constraints: + BoxConstraints(minWidth: childWidth, maxWidth: childWidth), + child: ControlWidget(key: key, control: ctrl), )); } @@ -80,33 +68,28 @@ class ResponsiveRowControl extends StatelessWidget with FletStoreMixin { spacing: bpSpacing - 0.1, runSpacing: getBreakpointNumber( runSpacing, view.size.width, view.breakpoints), - alignment: parseWrapAlignment( - control.attrString("alignment"), WrapAlignment.start)!, - crossAxisAlignment: parseWrapCrossAlignment( - control.attrString("verticalAlignment"), - WrapCrossAlignment.start)!, + alignment: control.getWrapAlignment( + "alignment", WrapAlignment.start)!, + crossAxisAlignment: control.getWrapCrossAlignment( + "vertical_alignment", WrapCrossAlignment.start)!, children: controls, ) : Row( spacing: bpSpacing - 0.1, - mainAxisAlignment: parseMainAxisAlignment( - control.attrString("alignment"), - MainAxisAlignment.start)!, + mainAxisAlignment: control.getMainAxisAlignment( + "alignment", MainAxisAlignment.start)!, mainAxisSize: MainAxisSize.max, - crossAxisAlignment: parseCrossAxisAlignment( - control.attrString("verticalAlignment"), - CrossAxisAlignment.start)!, + crossAxisAlignment: control.getCrossAxisAlignment( + "vertical_alignment", CrossAxisAlignment.start)!, children: controls, ); } catch (e) { - return ErrorControl( - "Error displaying ResponsiveRow", - description: e.toString(), - ); + return ErrorControl("Error displaying ResponsiveRow", + description: e.toString()); } }); - return constrainedControl(context, w, parent, control); + return ConstrainedControl(control: control, child: result); }); } } diff --git a/packages/flet/lib/src/controls/row.dart b/packages/flet/lib/src/controls/row.dart index f2f43c287..42b2ccde5 100644 --- a/packages/flet/lib/src/controls/row.dart +++ b/packages/flet/lib/src/controls/row.dart @@ -1,55 +1,39 @@ import 'package:flutter/widgets.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/alignment.dart'; -import 'create_control.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; import 'scroll_notification_control.dart'; import 'scrollable_control.dart'; class RowControl extends StatelessWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final bool? parentAdaptive; - final List children; - final FletControlBackend backend; - const RowControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const RowControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("Row build: ${control.id}"); - bool disabled = control.isDisabled || parentDisabled; - bool? adaptive = control.attrBool("adaptive") ?? parentAdaptive; - var spacing = control.attrDouble("spacing", 10)!; + var spacing = control.getDouble("spacing", 10)!; var mainAlignment = parseMainAxisAlignment( - control.attrString("alignment"), MainAxisAlignment.start)!; - var tight = control.attrBool("tight", false)!; - var wrap = control.attrBool("wrap", false)!; - var verticalAlignment = control.attrString("verticalAlignment"); - - List controls = children.where((c) => c.isVisible).map((c) { - return createControl(control, c.id, disabled, parentAdaptive: adaptive); - }).toList(); + control.getString("alignment"), MainAxisAlignment.start)!; + var tight = control.getBool("tight", false)!; + var wrap = control.getBool("wrap", false)!; + var verticalAlignment = control.getString("vertical_alignment"); + var controls = control.buildWidgets("controls"); Widget child = wrap ? Wrap( direction: Axis.horizontal, spacing: spacing, - runSpacing: control.attrDouble("runSpacing", 10)!, + runSpacing: control.getDouble("run_spacing", 10)!, alignment: parseWrapAlignment( - control.attrString("alignment"), WrapAlignment.start)!, + control.getString("alignment"), WrapAlignment.start)!, runAlignment: parseWrapAlignment( - control.attrString("runAlignment"), WrapAlignment.start)!, + control.getString("run_alignment"), WrapAlignment.start)!, crossAxisAlignment: parseWrapCrossAlignment( verticalAlignment, WrapCrossAlignment.center)!, children: controls, @@ -66,15 +50,12 @@ class RowControl extends StatelessWidget { child = ScrollableControl( control: control, scrollDirection: wrap ? Axis.vertical : Axis.horizontal, - backend: backend, - parentAdaptive: adaptive, child: child); - if (control.attrBool("onScroll", false)!) { - child = ScrollNotificationControl( - control: control, backend: backend, child: child); + if (control.getBool("on_scroll", false)!) { + child = ScrollNotificationControl(control: control, child: child); } - return constrainedControl(context, child, parent, control); + return ConstrainedControl(control: control, child: child); } } diff --git a/packages/flet/lib/src/controls/safe_area.dart b/packages/flet/lib/src/controls/safe_area.dart index 2984dbc85..f2fe1ddc5 100644 --- a/packages/flet/lib/src/controls/safe_area.dart +++ b/packages/flet/lib/src/controls/safe_area.dart @@ -1,49 +1,33 @@ import 'package:flutter/material.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/edge_insets.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; class SafeAreaControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - const SafeAreaControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive}); + const SafeAreaControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("SafeArea build: ${control.id}"); - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - bool disabled = control.isDisabled || parentDisabled; - bool? adaptive = control.attrBool("adaptive") ?? parentAdaptive; - var safeArea = SafeArea( - left: control.attrBool("left", true)!, - top: control.attrBool("top", true)!, - right: control.attrBool("right", true)!, - bottom: control.attrBool("bottom", true)!, + final safeArea = SafeArea( + left: control.getBool("avoid_intrusions_left", true)!, + top: control.getBool("avoid_intrusions_top", true)!, + right: control.getBool("avoid_intrusions_right", true)!, + bottom: control.getBool("avoid_intrusions_bottom", true)!, maintainBottomViewPadding: - control.attrBool("maintainBottomViewPadding", false)!, - minimum: parseEdgeInsets(control, "minimumPadding") ?? - parseEdgeInsets(control, "minimum") ?? - EdgeInsets.zero, - child: contentCtrls.isNotEmpty - ? createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: adaptive) - : const ErrorControl( + control.getBool("maintain_bottom_view_padding", false)!, + minimum: control.getEdgeInsets("minimum_padding", EdgeInsets.zero)!, + child: control.buildWidget("content") ?? + const ErrorControl( "SafeArea.content must be provided and visible")); - return constrainedControl(context, safeArea, parent, control); + return ConstrainedControl(control: control, child: safeArea); } } diff --git a/packages/flet/lib/src/controls/scroll_notification_control.dart b/packages/flet/lib/src/controls/scroll_notification_control.dart index 82f21e509..4a75085e5 100644 --- a/packages/flet/lib/src/controls/scroll_notification_control.dart +++ b/packages/flet/lib/src/controls/scroll_notification_control.dart @@ -1,20 +1,15 @@ -import 'dart:convert'; - import 'package:flutter/widgets.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; +import '../utils/numbers.dart'; class ScrollNotificationControl extends StatefulWidget { final Widget child; final Control control; - final FletControlBackend backend; - const ScrollNotificationControl( - {super.key, - required this.child, - required this.control, - required this.backend}); + ScrollNotificationControl( + {Key? key, required this.child, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => @@ -27,7 +22,7 @@ class _ScrollNotificationControlState extends State { @override Widget build(BuildContext context) { - _onScrollInterval = widget.control.attrInt("onScrollInterval", 10)!; + _onScrollInterval = widget.control.getInt("scroll_interval", 10)!; return NotificationListener( child: widget.child, @@ -36,52 +31,47 @@ class _ScrollNotificationControlState extends State { } bool _onNotification(ScrollNotification notification, BuildContext context) { - void sendEvent(dynamic eventData) { - var d = ""; - if (eventData is String) { - d = eventData; - } else if (eventData is Map) { - d = json.encode(eventData); - } + if (notification.depth != 0) return false; - debugPrint("ScrollNotification ${widget.control.id} event"); - widget.backend.triggerControlEvent(widget.control.id, "onScroll", d); + final eventType = notification.runtimeType.toString(); + final now = DateTime.now().millisecondsSinceEpoch; + final lastEventTimestamp = _lastEventTimestamps[eventType]; + + if (lastEventTimestamp != null && + now - lastEventTimestamp <= _onScrollInterval) { + return false; } - if (notification.depth == 0) { - var eventType = notification.runtimeType.toString(); - var now = DateTime.now().millisecondsSinceEpoch; - var lastEventTimestamp = _lastEventTimestamps[eventType]; - if (lastEventTimestamp == null || - now - lastEventTimestamp > _onScrollInterval) { - _lastEventTimestamps[eventType] = now; + _lastEventTimestamps[eventType] = now; - Map data = { - "p": notification.metrics.pixels, - "minse": notification.metrics.minScrollExtent, - "maxse": notification.metrics.maxScrollExtent, - "vd": notification.metrics.viewportDimension - }; - if (notification is ScrollStartNotification) { - data["t"] = "start"; - } else if (notification is ScrollUpdateNotification) { - data["t"] = "update"; - data["sd"] = notification.scrollDelta; - } else if (notification is ScrollEndNotification) { - data["t"] = "end"; - } else if (notification is UserScrollNotification) { - data["t"] = "user"; - data["dir"] = notification.direction.name; - } else if (notification is OverscrollNotification) { - data["t"] = "over"; - data["os"] = notification.overscroll; - data["v"] = notification.velocity; - } + final metrics = notification.metrics; + final Map fields = { + "pixels": metrics.pixels, + "min_scroll_extent": metrics.minScrollExtent, + "max_scroll_extent": metrics.maxScrollExtent, + "viewport_dimension": metrics.viewportDimension, + }; - if (data["t"] != null) { - sendEvent(data); - } - } + if (notification is ScrollStartNotification) { + fields["event_type"] = "start"; + } else if (notification is ScrollUpdateNotification) { + fields["event_type"] = "update"; + fields["scroll_delta"] = notification.scrollDelta; + } else if (notification is ScrollEndNotification) { + fields["event_type"] = "end"; + } else if (notification is UserScrollNotification) { + fields["event_type"] = "user"; + fields["direction"] = notification.direction.name; + } else if (notification is OverscrollNotification) { + fields["event_type"] = "overscroll"; + fields["overscroll"] = notification.overscroll; + fields["velocity"] = notification.velocity; + } + + // Check that event_type was set before triggering the event + if (fields["event_type"] != null) { + debugPrint("ScrollNotification ${widget.control.id} event"); + widget.control.triggerEvent("scroll", fields); } return false; diff --git a/packages/flet/lib/src/controls/scrollable_control.dart b/packages/flet/lib/src/controls/scrollable_control.dart index 5596c26a5..b7f3bf30e 100644 --- a/packages/flet/lib/src/controls/scrollable_control.dart +++ b/packages/flet/lib/src/controls/scrollable_control.dart @@ -1,32 +1,21 @@ -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; +import 'package:flet/flet.dart'; import 'package:flutter/material.dart'; -import '../flet_app_services.dart'; -import '../flet_control_backend.dart'; -import '../models/control.dart'; -import '../utils/animations.dart'; -import '../utils/numbers.dart'; -import '../utils/others.dart'; -import 'flet_store_mixin.dart'; +import '../utils/keys.dart'; class ScrollableControl extends StatefulWidget { final Control control; final Widget child; final Axis scrollDirection; final ScrollController? scrollController; - final bool? parentAdaptive; - final FletControlBackend backend; - const ScrollableControl( - {super.key, + ScrollableControl( + {Key? key, required this.control, required this.child, required this.scrollDirection, - this.scrollController, - required this.parentAdaptive, - required this.backend}); + this.scrollController}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _ScrollableControlState(); @@ -36,11 +25,11 @@ class _ScrollableControlState extends State with FletStoreMixin { late final ScrollController _controller; late bool _ownController = false; - String? _method; @override void initState() { super.initState(); + widget.control.addInvokeMethodListener(_invokeMethod); if (widget.scrollController != null) { _controller = widget.scrollController!; } else { @@ -49,114 +38,87 @@ class _ScrollableControlState extends State } } + Future _invokeMethod(String name, dynamic args) async { + debugPrint("ScrollableControl.$name($args)"); + var offset = parseDouble(args["offset"]); + var delta = parseDouble(args["delta"]); + var scrollKey = parseKey(args["scroll_key"]); + var globalKey = scrollKey != null + ? widget.control.backend.globalKeys[scrollKey.toString()] + : null; + switch (name) { + case "scroll_to": + var duration = parseDuration(args["duration"], Duration.zero)!; + var curve = parseCurve(args["curve"], Curves.ease)!; + if (globalKey != null) { + var ctx = globalKey.currentContext; + if (ctx != null) { + Scrollable.ensureVisible(ctx, duration: duration, curve: curve); + } + } else if (offset != null) { + if (offset < 0) { + offset = _controller.position.maxScrollExtent + offset + 1; + } + if (duration.inMilliseconds < 1) { + _controller.jumpTo(offset); + } else { + _controller.animateTo(offset, duration: duration, curve: curve); + } + } else if (delta != null) { + var offset = _controller.position.pixels + delta; + if (duration.inMilliseconds < 1) { + _controller.jumpTo(offset); + } else { + _controller.animateTo(offset, duration: duration, curve: curve); + } + } + } + } + @override void dispose() { if (_ownController) { _controller.dispose(); } + widget.control.removeInvokeMethodListener(_invokeMethod); super.dispose(); } @override Widget build(BuildContext context) { - return withPagePlatform((context, platform) { - ScrollMode scrollMode = parseScrollMode( - widget.control.attrString("scroll"), ScrollMode.none)!; - - var method = widget.control.attrString("method"); - - if (widget.control.attrBool("autoScroll", false)!) { - WidgetsBinding.instance.addPostFrameCallback((_) { - _controller.animateTo( - _controller.position.maxScrollExtent, - duration: const Duration(seconds: 1), - curve: Curves.ease, - ); - }); - } else if (method != null && method != _method) { - _method = method; - debugPrint("ScrollableControl JSON method: $method"); - widget.backend.updateControlState(widget.control.id, {"method": ""}); - - var mj = json.decode(method); - var name = mj["n"] as String; - var params = Map.from(mj["p"] as Map); + debugPrint("ScrollableControl build: ${widget.control.id}"); + ScrollMode scrollMode = + widget.control.getScrollMode("scroll", ScrollMode.none)!; - if (name == "scroll_to") { - var duration = parseInt(params["duration"], 0)!; - var curve = parseCurve(params["curve"], Curves.ease)!; - if (params["key"] != null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - var key = FletAppServices.of(context).globalKeys[params["key"]]; - if (key != null) { - var ctx = key.currentContext; - if (ctx != null) { - Scrollable.ensureVisible(ctx, - duration: duration > 0 - ? Duration(milliseconds: duration) - : Duration.zero, - curve: curve); - } - } - }); - } else if (params["offset"] != null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - var offset = parseDouble(params["offset"], 0)!; - if (offset < 0) { - offset = _controller.position.maxScrollExtent + offset + 1; - } - if (duration < 1) { - _controller.jumpTo(offset); - } else { - _controller.animateTo( - offset, - duration: Duration(milliseconds: duration), - curve: curve, - ); - } - }); - } else if (params["delta"] != null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - var delta = parseDouble(params["delta"], 0)!; - var offset = _controller.position.pixels + delta; - if (duration < 1) { - _controller.jumpTo(offset); - } else { - _controller.animateTo( - offset, - duration: Duration(milliseconds: duration), - curve: curve, - ); - } - }); - } - } - } - - return scrollMode != ScrollMode.none - ? Scrollbar( - thumbVisibility: scrollMode == ScrollMode.always || - (scrollMode == ScrollMode.adaptive && - !kIsWeb && - platform != TargetPlatform.iOS && - platform != TargetPlatform.android) - ? true - : false, - trackVisibility: scrollMode == ScrollMode.hidden ? false : null, - thickness: scrollMode == ScrollMode.hidden - ? 0 - : platform == TargetPlatform.iOS || - platform == TargetPlatform.android - ? 4.0 - : null, - //interactive: true, + if (widget.control.getBool("auto_scroll", false)!) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _controller.animateTo( + _controller.position.maxScrollExtent, + duration: const Duration(seconds: 1), + curve: Curves.ease, + ); + }); + } + return scrollMode != ScrollMode.none + ? Scrollbar( + // todo: create class ScrollBarConfiguration on Py end, for more customizability + thumbVisibility: scrollMode == ScrollMode.always || + (scrollMode == ScrollMode.adaptive && !isMobilePlatform()) + ? true + : false, + trackVisibility: scrollMode == ScrollMode.hidden ? false : null, + thickness: scrollMode == ScrollMode.hidden + ? 0 + : isMobilePlatform() + ? 4.0 + : null, + //interactive: true, + controller: _controller, + child: SingleChildScrollView( controller: _controller, - child: SingleChildScrollView( - controller: _controller, - scrollDirection: widget.scrollDirection, - child: widget.child, - )) - : widget.child; - }); + scrollDirection: widget.scrollDirection, + child: widget.child, + )) + : widget.child; } } diff --git a/packages/flet/lib/src/controls/search_anchor.dart b/packages/flet/lib/src/controls/search_anchor.dart deleted file mode 100644 index 500a2ed0d..000000000 --- a/packages/flet/lib/src/controls/search_anchor.dart +++ /dev/null @@ -1,317 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; - -import '../flet_control_backend.dart'; -import '../models/control.dart'; -import '../utils/borders.dart'; -import '../utils/box.dart'; -import '../utils/colors.dart'; -import '../utils/edge_insets.dart'; -import '../utils/form_field.dart'; -import '../utils/numbers.dart'; -import '../utils/text.dart'; -import 'create_control.dart'; - -class SearchAnchorControl extends StatefulWidget { - final Control? parent; - final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - - const SearchAnchorControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); - - @override - State createState() => _SearchAnchorControlState(); -} - -class _SearchAnchorControlState extends State { - late final SearchController _controller; - bool _focused = false; - TextCapitalization _textCapitalization = TextCapitalization.none; - late final FocusNode _focusNode; - String? _lastFocusValue; - String? _lastBlurValue; - - @override - void initState() { - super.initState(); - _controller = SearchController(); - _controller.addListener(_searchTextChanged); - _focusNode = FocusNode(); - _focusNode.addListener(_onFocusChange); - } - - void _onFocusChange() { - setState(() { - _focused = _focusNode.hasFocus; - }); - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); - } - - @override - void dispose() { - _controller.removeListener(_searchTextChanged); - _controller.dispose(); - _focusNode.removeListener(_onFocusChange); - _focusNode.dispose(); - super.dispose(); - } - - void _searchTextChanged() { - _textCapitalization = parseTextCapitalization( - widget.control.attrString("capitalization"), TextCapitalization.none)!; - _updateValue(_controller.text); - } - - void _updateValue(String value) { - value = applyCapitalization(value); - if (_controller.value.text != value) { - _controller.value = TextEditingValue( - text: value, - selection: TextSelection.collapsed(offset: value.length), - ); - } - - widget.backend.updateControlState(widget.control.id, {"value": value}); - } - - String applyCapitalization(String text) { - switch (_textCapitalization) { - /// Capitalizes the first character of each word. - case TextCapitalization.words: - return text - .split(RegExp(r'\s+')) - .map((word) => word.isNotEmpty - ? word[0].toUpperCase() + word.substring(1).toLowerCase() - : word) - .join(' '); - - /// Capitalizes the first character of each sentence. - case TextCapitalization.sentences: - return text - .split('. ') - .map((sentence) => sentence.isNotEmpty - ? sentence.trimLeft()[0].toUpperCase() + - sentence.substring(1).toLowerCase() - : sentence) - .join('. '); - - /// Capitalizes all characters. - case TextCapitalization.characters: - return text.toUpperCase(); - - /// No change. - case TextCapitalization.none: - return text; - } - } - - @override - Widget build(BuildContext context) { - debugPrint("SearchAnchor build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - var value = widget.control.attrString("value", ""); - if (value != null && value != _controller.text) { - WidgetsBinding.instance.addPostFrameCallback((_) { - _controller.text = value; - }); - } - - bool onChange = widget.control.attrBool("onChange", false)!; - bool onTap = widget.control.attrBool("onTap", false)!; - bool onSubmit = widget.control.attrBool("onSubmit", false)!; - - var suggestionCtrls = - widget.children.where((c) => c.name == "controls" && c.isVisible); - var barLeadingCtrls = - widget.children.where((c) => c.name == "barLeading" && c.isVisible); - var barTrailingCtrls = - widget.children.where((c) => c.name == "barTrailing" && c.isVisible); - var viewLeadingCtrls = - widget.children.where((c) => c.name == "viewLeading" && c.isVisible); - var viewTrailingCtrls = - widget.children.where((c) => c.name == "viewTrailing" && c.isVisible); - TextInputType keyboardType = parseTextInputType( - widget.control.attrString("keyboardType"), TextInputType.text)!; - - var focusValue = widget.control.attrString("focus"); - var blurValue = widget.control.attrString("blur"); - if (focusValue != null && focusValue != _lastFocusValue) { - _lastFocusValue = focusValue; - _focusNode.requestFocus(); - } - if (blurValue != null && blurValue != _lastBlurValue) { - _lastBlurValue = blurValue; - _focusNode.unfocus(); - } - - var method = widget.control.attrString("method"); - - if (method != null) { - debugPrint("SearchAnchor JSON method: $method"); - - void resetMethod() { - widget.backend.updateControlState(widget.control.id, {"method": ""}); - } - - var mj = json.decode(method); - var name = mj["n"] as String; - var params = Map.from(mj["p"] as Map); - - if (name == "closeView") { - WidgetsBinding.instance.addPostFrameCallback((_) { - resetMethod(); - if (_controller.isOpen) { - var text = params["text"].toString(); - _updateValue(text); - _controller.closeView(text); - } - }); - } else if (name == "openView") { - WidgetsBinding.instance.addPostFrameCallback((_) { - resetMethod(); - if (!_controller.isOpen) { - _controller.openView(); - } - }); - } - } - - Widget anchor = SearchAnchor( - searchController: _controller, - headerHintStyle: parseTextStyle( - Theme.of(context), widget.control, "viewHintTextStyle"), - headerTextStyle: parseTextStyle( - Theme.of(context), widget.control, "viewHeaderTextStyle"), - viewSide: - parseBorderSide(Theme.of(context), widget.control, "viewSide"), - isFullScreen: widget.control.attrBool("fullScreen", false), - viewBackgroundColor: widget.control.attrColor("viewBgcolor", context), - dividerColor: widget.control.attrColor("dividerColor", context), - viewHintText: widget.control.attrString("viewHintText"), - viewElevation: widget.control.attrDouble("viewElevation"), - headerHeight: widget.control.attrDouble("viewHeaderHeight"), - viewConstraints: - parseBoxConstraints(widget.control, "viewSizeConstraints"), - viewShape: parseOutlinedBorder(widget.control, "viewShape"), - viewTrailing: viewTrailingCtrls.isNotEmpty - ? viewTrailingCtrls.map((ctrl) { - return createControl(widget.parent, ctrl.id, disabled, - parentAdaptive: widget.parentAdaptive); - }) - : null, - viewLeading: viewLeadingCtrls.isNotEmpty - ? createControl(widget.parent, viewLeadingCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : null, - viewOnSubmitted: onSubmit - ? (String value) { - debugPrint("SearchBar.onSubmit: $value"); - _updateValue(value); - widget.backend - .triggerControlEvent(widget.control.id, "submit", value); - } - : null, - viewOnChanged: onChange - ? (String value) { - debugPrint("SearchBar.onChange: $value"); - _updateValue(value); - widget.backend - .triggerControlEvent(widget.control.id, "change", value); - } - : null, - viewSurfaceTintColor: - widget.control.attrColor("viewSurfaceTintColor", context), - textCapitalization: _textCapitalization, - keyboardType: keyboardType, - builder: (BuildContext context, SearchController controller) { - return SearchBar( - controller: controller, - keyboardType: keyboardType, - textCapitalization: _textCapitalization, - autoFocus: widget.control.attrBool("autoFocus", false)!, - focusNode: _focusNode, - hintText: widget.control.attrString("barHintText"), - elevation: parseWidgetStateDouble(widget.control, "barElevation"), - shape: parseWidgetStateOutlinedBorder(widget.control, "barShape"), - padding: parseWidgetStateEdgeInsets(widget.control, "barPadding"), - textStyle: parseWidgetStateTextStyle( - Theme.of(context), widget.control, "barTextStyle"), - hintStyle: parseWidgetStateTextStyle( - Theme.of(context), widget.control, "barHintTextStyle"), - shadowColor: parseWidgetStateColor( - Theme.of(context), widget.control, "barShadowColor"), - surfaceTintColor: parseWidgetStateColor( - Theme.of(context), widget.control, "barSurfaceTintColor"), - side: parseWidgetStateBorderSide( - Theme.of(context), widget.control, "barBorderSide"), - backgroundColor: parseWidgetStateColor( - Theme.of(context), widget.control, "barBgcolor"), - overlayColor: parseWidgetStateColor( - Theme.of(context), widget.control, "barOverlayColor"), - scrollPadding: parseEdgeInsets(widget.control, "barScrollPadding", - const EdgeInsets.all(20.0))!, - leading: barLeadingCtrls.isNotEmpty - ? createControl( - widget.parent, barLeadingCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive) - : null, - trailing: barTrailingCtrls.isNotEmpty - ? barTrailingCtrls.map((ctrl) { - return createControl(widget.parent, ctrl.id, disabled, - parentAdaptive: widget.parentAdaptive); - }) - : null, - onTap: onTap - ? () { - widget.backend - .triggerControlEvent(widget.control.id, "tap"); - } - : null, - onTapOutside: widget.control.attrBool("onTapOutsideBar", false)! - ? (PointerDownEvent? event) { - widget.backend.triggerControlEvent( - widget.control.id, "tapOutsideBar"); - } - : null, - onSubmitted: onSubmit - ? (String value) { - debugPrint("SearchBar.onSubmit: $value"); - _updateValue(value); - widget.backend.triggerControlEvent( - widget.control.id, "submit", value); - } - : null, - onChanged: onChange - ? (String value) { - debugPrint("SearchBar.onChange: $value"); - _updateValue(value); - widget.backend.triggerControlEvent( - widget.control.id, "change", value); - } - : null, - ); - }, - suggestionsBuilder: - (BuildContext context, SearchController controller) { - return suggestionCtrls.map((ctrl) { - return createControl(widget.parent, ctrl.id, disabled, - parentAdaptive: widget.parentAdaptive); - }); - }); - - return constrainedControl(context, anchor, widget.parent, widget.control); - } -} diff --git a/packages/flet/lib/src/controls/search_bar.dart b/packages/flet/lib/src/controls/search_bar.dart new file mode 100644 index 000000000..4f25b580b --- /dev/null +++ b/packages/flet/lib/src/controls/search_bar.dart @@ -0,0 +1,252 @@ +import 'package:flutter/material.dart'; + +import '../extensions/control.dart'; +import '../models/control.dart'; +import '../utils/borders.dart'; +import '../utils/box.dart'; +import '../utils/colors.dart'; +import '../utils/edge_insets.dart'; +import '../utils/form_field.dart'; +import '../utils/numbers.dart'; +import '../utils/text.dart'; +import 'base_controls.dart'; + +class SearchBarControl extends StatefulWidget { + final Control control; + + SearchBarControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); + + @override + State createState() => _SearchBarControlState(); +} + +class _SearchBarControlState extends State { + late final SearchController _controller; + bool _focused = false; + TextCapitalization _textCapitalization = TextCapitalization.none; + late final FocusNode _focusNode; + String? _lastFocusValue; + String? _lastBlurValue; + + @override + void initState() { + super.initState(); + _controller = SearchController(); + _controller.addListener(_searchTextChanged); + _focusNode = FocusNode(); + _focusNode.addListener(_onFocusChange); + widget.control.addInvokeMethodListener(_invokeMethod); + } + + void _onFocusChange() { + setState(() { + _focused = _focusNode.hasFocus; + }); + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); + } + + @override + void dispose() { + _controller.removeListener(_searchTextChanged); + _controller.dispose(); + _focusNode.removeListener(_onFocusChange); + _focusNode.dispose(); + widget.control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } + + void _searchTextChanged() { + _textCapitalization = parseTextCapitalization( + widget.control.getString("capitalization"), TextCapitalization.none)!; + _updateValue(_controller.text); + } + + void _updateValue(String value) { + value = applyCapitalization(value); + if (_controller.value.text != value) { + _controller.value = TextEditingValue( + text: value, + selection: TextSelection.collapsed(offset: value.length), + ); + } + + widget.control.updateProperties({"value": value}); + } + + Future _invokeMethod(String name, dynamic args) async { + debugPrint("SearchBar.$name($args)"); + switch (name) { + case "close_view": + if (_controller.isOpen) { + var text = args["text"]; + _updateValue(text); + _controller.closeView(text); + } + case "open_view": + if (!_controller.isOpen) { + _controller.openView(); + } + case "focus": + _focusNode.requestFocus(); + case "blur": + // todo: test this method + _focusNode.unfocus( + disposition: UnfocusDisposition.previouslyFocusedChild); + default: + throw Exception("Unknown SearchBar method: $name"); + } + } + + String applyCapitalization(String text) { + switch (_textCapitalization) { + /// Capitalizes the first character of each word. + case TextCapitalization.words: + return text + .split(RegExp(r'\s+')) + .map((word) => word.isNotEmpty + ? word[0].toUpperCase() + word.substring(1).toLowerCase() + : word) + .join(' '); + + /// Capitalizes the first character of each sentence. + case TextCapitalization.sentences: + return text + .split('. ') + .map((sentence) => sentence.isNotEmpty + ? sentence.trimLeft()[0].toUpperCase() + + sentence.substring(1).toLowerCase() + : sentence) + .join('. '); + + /// Capitalizes all characters. + case TextCapitalization.characters: + return text.toUpperCase(); + + /// No change. + case TextCapitalization.none: + return text; + } + } + + @override + Widget build(BuildContext context) { + debugPrint("SearchAnchor build: ${widget.control.id}"); + + var value = widget.control.getString("value", "")!; + if (value != _controller.text) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _controller.text = value; + }); + } + + bool onChange = widget.control.getBool("on_change", false)!; + bool onTap = widget.control.getBool("on_tap", false)!; + bool onSubmit = widget.control.getBool("on_submit", false)!; + TextInputType keyboardType = + widget.control.getTextInputType("keyboard_type", TextInputType.text)!; + + var focusValue = widget.control.getString("focus"); + var blurValue = widget.control.getString("blur"); + if (focusValue != null && focusValue != _lastFocusValue) { + _lastFocusValue = focusValue; + _focusNode.requestFocus(); + } + if (blurValue != null && blurValue != _lastBlurValue) { + _lastBlurValue = blurValue; + _focusNode.unfocus(); + } + + var theme = Theme.of(context); + + Widget anchor = SearchAnchor( + searchController: _controller, + headerHintStyle: + widget.control.getTextStyle("view_hint_text_style", theme), + headerTextStyle: + widget.control.getTextStyle("view_header_text_style", theme), + viewSide: widget.control.getBorderSide("view_side", theme), + isFullScreen: widget.control.getBool("full_screen", false), + viewBackgroundColor: widget.control.getColor("view_bgcolor", context), + dividerColor: widget.control.getColor("divider_color", context), + viewHintText: widget.control.getString("view_hint_text"), + viewElevation: widget.control.getDouble("view_elevation"), + headerHeight: widget.control.getDouble("view_header_height"), + viewConstraints: + widget.control.getBoxConstraints("view_size_constraints"), + viewShape: widget.control.getShape("view_shape", theme), + viewTrailing: widget.control.buildWidgets("view_trailing"), + viewLeading: widget.control.buildWidget("view_leading"), + viewOnSubmitted: onSubmit + ? (String value) { + _updateValue(value); + widget.control.triggerEvent("submit", value); + } + : null, + viewOnChanged: onChange + ? (String value) { + _updateValue(value); + widget.control.triggerEvent("change", value); + } + : null, + viewSurfaceTintColor: + widget.control.getColor("view_surface_tint_color", context), + textCapitalization: _textCapitalization, + keyboardType: keyboardType, + builder: (BuildContext context, SearchController controller) { + return SearchBar( + controller: controller, + keyboardType: keyboardType, + textCapitalization: _textCapitalization, + autoFocus: widget.control.getBool("autofocus", false)!, + focusNode: _focusNode, + hintText: widget.control.getString("bar_hint_text"), + elevation: widget.control.getWidgetStateDouble("bar_elevation"), + shape: + widget.control.getWidgetStateOutlinedBorder("bar_shape", theme), + padding: widget.control.getWidgetStatePadding("bar_padding"), + textStyle: + widget.control.getWidgetStateTextStyle("bar_text_style", theme), + hintStyle: widget.control + .getWidgetStateTextStyle("bar_hint_text_style", theme), + shadowColor: + widget.control.getWidgetStateColor("bar_shadow_color", theme), + surfaceTintColor: widget.control + .getWidgetStateColor("bar_surface_tint_color", theme), + side: widget.control + .getWidgetStateBorderSide("bar_border_side", theme), + backgroundColor: + widget.control.getWidgetStateColor("bar_bgcolor", theme), + overlayColor: + widget.control.getWidgetStateColor("bar_overlay_color", theme), + scrollPadding: widget.control + .getPadding("bar_scroll_padding", const EdgeInsets.all(20.0))!, + leading: widget.control.buildWidget("bar_leading"), + trailing: widget.control.buildWidgets("bar_trailing"), + onTap: onTap ? () => widget.control.triggerEvent("tap") : null, + onTapOutside: widget.control.getBool("on_tap_outside_bar", false)! + ? (PointerDownEvent? event) => + widget.control.triggerEvent("tap_outside_bar") + : null, + onSubmitted: onSubmit + ? (String value) { + _updateValue(value); + widget.control.triggerEvent("submit", value); + } + : null, + onChanged: onChange + ? (String value) { + _updateValue(value); + widget.control.triggerEvent("change", value); + } + : null, + ); + }, + suggestionsBuilder: + (BuildContext context, SearchController controller) { + return widget.control.buildWidgets("controls"); + }); + + return ConstrainedControl(control: widget.control, child: anchor); + } +} diff --git a/packages/flet/lib/src/controls/segmented_button.dart b/packages/flet/lib/src/controls/segmented_button.dart index 2140eaf2b..255fe14b1 100644 --- a/packages/flet/lib/src/controls/segmented_button.dart +++ b/packages/flet/lib/src/controls/segmented_button.dart @@ -1,30 +1,20 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/buttons.dart'; import '../utils/edge_insets.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'error.dart'; -import 'flet_store_mixin.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; +import '../widgets/error.dart'; +import '../widgets/flet_store_mixin.dart'; +import 'base_controls.dart'; class SegmentedButtonControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final FletControlBackend backend; - const SegmentedButtonControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.backend}); + SegmentedButtonControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _SegmentedButtonControlState(); @@ -33,9 +23,9 @@ class SegmentedButtonControl extends StatefulWidget { class _SegmentedButtonControlState extends State with FletStoreMixin { void onChange(Set selection) { - var s = jsonEncode(selection.toList()); - widget.backend.updateControlState(widget.control.id, {"selected": s}); - widget.backend.triggerControlEvent(widget.control.id, "change", s); + var s = selection.toList(); // todo: support Sets on both ends (msgpack) + widget.control.updateProperties({"selected": s}, notify: true); + widget.control.triggerEvent("change", s); } @override @@ -43,7 +33,7 @@ class _SegmentedButtonControlState extends State debugPrint("SegmentedButtonControl build: ${widget.control.id}"); var theme = Theme.of(context); - var style = parseButtonStyle(Theme.of(context), widget.control, "style", + var style = widget.control.getButtonStyle("style", Theme.of(context), defaultForegroundColor: theme.colorScheme.primary, defaultBackgroundColor: theme.colorScheme.surface, defaultOverlayColor: theme.colorScheme.primary.withOpacity(0.08), @@ -55,36 +45,31 @@ class _SegmentedButtonControlState extends State ? const StadiumBorder() : RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))); - bool allowEmptySelection = - widget.control.attrBool("allowEmptySelection", false)!; - - bool allowMultipleSelection = - widget.control.attrBool("allowMultipleSelection", false)!; - - Set selected = - (jsonDecode(widget.control.attrString("selected", "[]")!) as List) - .map((e) => e.toString()) - .toSet(); - - List segments = widget.children - .where((c) => c.name == "segment" && c.isVisible) - .toList(); + var allowEmptySelection = + widget.control.getBool("allow_empty_selection", false)!; + var allowMultipleSelection = + widget.control.getBool("allow_multiple_selection", false)!; + var selected = widget.control + .get("selected", [])! + .map((e) => e.toString()) + .toSet(); + var segments = widget.control.children("segments"); if (segments.isEmpty) { return const ErrorControl( - "SegmentedButton.segments must be provided and contain at minimum one visible segment"); + "SegmentedButton.segments must be contain at least one visible segment"); } if (selected.isEmpty && !allowEmptySelection) { return const ErrorControl( - "SegmentedButton.selected must be provided and contain at minimum one value because allow_empty_selection=False"); + "SegmentedButton.selected must contain at least one value because allow_empty_selection=False"); } if (!allowMultipleSelection && selected.length != 1 && !allowEmptySelection) { return const ErrorControl( - "SegmentedButton.selected must be provided and contain exactly one value because allow_multiple_selection=False"); + "SegmentedButton.selected must contain exactly one value because allow_multiple_selection=False"); } if (allowMultipleSelection && selected.length > segments.length) { @@ -92,54 +77,27 @@ class _SegmentedButtonControlState extends State "The length of SegmentedButton.selected must be less than or equal to the number of visible segments"); } - var selectedIcon = - widget.children.where((c) => c.name == "selectedIcon" && c.isVisible); - - bool showSelectedIcon = widget.control.attrBool("showSelectedIcon", true)!; - - bool disabled = widget.control.isDisabled || widget.parentDisabled; - debugPrint("SegmentedButtonControl build: ${widget.control.id}"); - - var sb = withControls(segments.map((s) => s.id), (content, segmentViews) { - return SegmentedButton( - emptySelectionAllowed: allowEmptySelection, - multiSelectionEnabled: allowMultipleSelection, - selected: selected.isNotEmpty ? selected : {}, - showSelectedIcon: showSelectedIcon, - style: style, - selectedIcon: selectedIcon.isNotEmpty - ? createControl(widget.control, selectedIcon.first.id, disabled) - : null, - onSelectionChanged: !disabled - ? (newSelection) { - onChange(newSelection.toSet()); - } - : null, - direction: parseAxis( - widget.control.attrString("direction"), Axis.horizontal)!, - expandedInsets: parseEdgeInsets(widget.control, "padding"), - segments: segmentViews.controlViews.map((segmentView) { - var iconCtrls = segmentView.children - .where((c) => c.name == "icon" && c.isVisible); - var labelCtrls = segmentView.children - .where((c) => c.name == "label" && c.isVisible); - var segmentDisabled = segmentView.control.isDisabled || disabled; - var segmentTooltip = segmentView.control.attrString("tooltip"); - return ButtonSegment( - value: segmentView.control.attrString("value")!, - enabled: !segmentDisabled, - tooltip: segmentDisabled ? null : segmentTooltip, - icon: iconCtrls.isNotEmpty - ? createControl(segmentView.control, iconCtrls.first.id, - segmentDisabled) - : null, - label: labelCtrls.isNotEmpty - ? createControl(segmentView.control, labelCtrls.first.id, - segmentDisabled) - : null); - }).toList()); - }); - - return constrainedControl(context, sb, widget.parent, widget.control); + var segmentedButton = SegmentedButton( + emptySelectionAllowed: allowEmptySelection, + multiSelectionEnabled: allowMultipleSelection, + selected: selected, + showSelectedIcon: widget.control.getBool("show_selected_icon", true)!, + style: style, + selectedIcon: widget.control.buildWidget("selected_icon"), + onSelectionChanged: !widget.control.disabled + ? (newSelection) => onChange(newSelection) + : null, + direction: widget.control.getAxis("direction", Axis.horizontal)!, + expandedInsets: widget.control.getPadding("padding"), + segments: segments.map((segment) { + return ButtonSegment( + value: segment.getString("value")!, + enabled: !segment.disabled, + tooltip: segment.disabled ? null : segment.getString("tooltip"), + icon: segment.buildIconOrWidget("icon"), + label: segment.buildTextOrWidget("label")); + }).toList()); + + return ConstrainedControl(control: widget.control, child: segmentedButton); } } diff --git a/packages/flet/lib/src/controls/selection_area.dart b/packages/flet/lib/src/controls/selection_area.dart index ecdae11ff..f826b2b2d 100644 --- a/packages/flet/lib/src/controls/selection_area.dart +++ b/packages/flet/lib/src/controls/selection_area.dart @@ -1,47 +1,32 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; class SelectionAreaControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const SelectionAreaControl( - {super.key, - required this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const SelectionAreaControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("SelectionArea build: ${control.id}"); - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - if (contentCtrls.isEmpty) { + var content = control.buildWidget("content"); + if (content == null) { return const ErrorControl( "SelectionArea.content must be provided and visible"); } - bool disabled = control.isDisabled || parentDisabled; var selectionArea = SelectionArea( - child: createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: parentAdaptive), + child: content, onSelectionChanged: (SelectedContent? selection) { - backend.triggerControlEvent(control.id, "change", selection?.plainText); + control.triggerEvent("change", selection?.plainText); }, ); - return baseControl(context, selectionArea, parent, control); + return BaseControl(control: control, child: selectionArea); } } diff --git a/packages/flet/lib/src/controls/semantics.dart b/packages/flet/lib/src/controls/semantics.dart index 6f0b89292..021af7b6a 100644 --- a/packages/flet/lib/src/controls/semantics.dart +++ b/packages/flet/lib/src/controls/semantics.dart @@ -1,165 +1,109 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; -import 'create_control.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; class SemanticsControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const SemanticsControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + const SemanticsControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("Semantics build: ${control.id}"); - - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - bool disabled = control.isDisabled || parentDisabled; - Semantics semantics = Semantics( - label: control.attrString("label"), - enabled: !disabled, - expanded: control.attrBool("expanded"), - hidden: control.attrBool("hidden"), - selected: control.attrBool("selected"), - checked: control.attrBool("checked"), - button: control.attrBool("button"), - slider: control.attrBool("slider"), - value: control.attrString("value"), - textField: control.attrBool("textField"), - image: control.attrBool("image"), - link: control.attrBool("link"), - header: control.attrBool("header"), - increasedValue: control.attrString("increasedValue"), - decreasedValue: control.attrString("decreasedValue"), - hint: control.attrString("hint"), - onTapHint: control.attrString("onTapHint"), - onLongPressHint: control.attrString("onLongPressHint"), - container: control.attrBool("container")!, - liveRegion: control.attrBool("liveRegion"), - obscured: control.attrBool("obscured"), - multiline: control.attrBool("multiline"), - focused: control.attrBool("focused"), - readOnly: control.attrBool("readOnly"), - focusable: control.attrBool("focusable"), - tooltip: control.attrString("tooltip"), - toggled: control.attrBool("toggled"), - maxValueLength: control.attrInt("maxValueLength"), - currentValueLength: control.attrInt("currentValueLength"), - headingLevel: control.attrInt("headingLevel"), - excludeSemantics: control.attrBool("excludeSemantics", false)!, - mixed: control.attrBool("mixed"), - onTap: control.attrBool("onclick", false)! - ? () { - backend.triggerControlEvent(control.id, "click"); - } - : null, - onIncrease: control.attrBool("onIncrease", false)! - ? () { - backend.triggerControlEvent(control.id, "increase"); - } - : null, - onDecrease: control.attrBool("onDecrease", false)! - ? () { - backend.triggerControlEvent(control.id, "decrease"); - } - : null, - onDismiss: control.attrBool("onDismiss", false)! - ? () { - backend.triggerControlEvent(control.id, "dismiss"); - } - : null, - onScrollLeft: control.attrBool("onScrollLeft", false)! - ? () { - backend.triggerControlEvent(control.id, "scrollLeft"); - } - : null, - onScrollRight: control.attrBool("onScrollRight", false)! - ? () { - backend.triggerControlEvent(control.id, "scrollRight"); - } - : null, - onScrollUp: control.attrBool("onScrollUp", false)! - ? () { - backend.triggerControlEvent(control.id, "scrollUp"); - } - : null, - onScrollDown: control.attrBool("onScrollDown", false)! - ? () { - backend.triggerControlEvent(control.id, "scrollDown"); - } - : null, - onCopy: control.attrBool("onCopy", false)! - ? () { - backend.triggerControlEvent(control.id, "copy"); - } - : null, - onCut: control.attrBool("onCut", false)! - ? () { - backend.triggerControlEvent(control.id, "cut"); - } - : null, - onPaste: control.attrBool("onPaste", false)! - ? () { - backend.triggerControlEvent(control.id, "paste"); - } - : null, - onLongPress: control.attrBool("onDismiss", false)! - ? () { - backend.triggerControlEvent(control.id, "dismiss"); - } - : null, - onMoveCursorForwardByCharacter: - control.attrBool("onMoveCursorForwardByCharacter", false)! - ? (bool value) { - backend.triggerControlEvent(control.id, - "move_cursor_forward_by_character", value.toString()); - } - : null, - onMoveCursorBackwardByCharacter: - control.attrBool("onMoveCursorBackwardByCharacter", false)! - ? (bool value) { - backend.triggerControlEvent(control.id, - "move_cursor_backward_by_character", value.toString()); - } - : null, - onDidGainAccessibilityFocus: - control.attrBool("onDidGainAccessibilityFocus", false)! - ? () { - backend.triggerControlEvent( - control.id, "did_gain_accessibility_focus"); - } - : null, - onDidLoseAccessibilityFocus: - control.attrBool("onDidLoseAccessibilityFocus", false)! - ? () { - backend.triggerControlEvent( - control.id, "did_lose_accessibility_focus"); - } - : null, - onSetText: control.attrBool("onSetText", false)! - ? (String text) { - backend.triggerControlEvent(control.id, "set_text", text); - } - : null, - child: contentCtrls.isNotEmpty - ? createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: parentAdaptive) - : null); + label: control.getString("label"), + enabled: !control.disabled, + expanded: control.getBool("expanded"), + hidden: control.getBool("hidden"), + selected: control.getBool("selected"), + checked: control.getBool("checked"), + button: control.getBool("button"), + slider: control.getBool("slider"), + value: control.getString("value"), + textField: control.getBool("text_field"), + image: control.getBool("image"), + link: control.getBool("link"), + header: control.getBool("header"), + increasedValue: control.getString("increased_value"), + decreasedValue: control.getString("decreased_value"), + hint: control.getString("hint"), + onTapHint: control.getString("on_tap_hint"), + onLongPressHint: control.getString("on_long_press_hint"), + container: control.getBool("container")!, + liveRegion: control.getBool("live_region"), + obscured: control.getBool("obscured"), + multiline: control.getBool("multiline"), + focused: control.getBool("focused"), + readOnly: control.getBool("read_only"), + focusable: control.getBool("focusable"), + tooltip: control.getString("tooltip"), + toggled: control.getBool("toggled"), + maxValueLength: control.getInt("max_value_length"), + currentValueLength: control.getInt("current_value_length"), + headingLevel: control.getInt("heading_level"), + excludeSemantics: control.getBool("exclude_semantics", false)!, + mixed: control.getBool("mixed"), + onTap: control.getBool("on_click", false)! + ? () => control.triggerEvent("click") + : null, + onIncrease: control.getBool("on_increase", false)! + ? () => control.triggerEvent("increase") + : null, + onDecrease: control.getBool("on_decrease", false)! + ? () => control.triggerEvent("decrease") + : null, + onDismiss: control.getBool("on_dismiss", false)! + ? () => control.triggerEvent("dismiss") + : null, + onScrollLeft: control.getBool("on_scroll_left", false)! + ? () => control.triggerEvent("scroll_left") + : null, + onScrollRight: control.getBool("on_scroll_right", false)! + ? () => control.triggerEvent("scroll_right") + : null, + onScrollUp: control.getBool("on_scroll_up", false)! + ? () => control.triggerEvent("scroll_up") + : null, + onScrollDown: control.getBool("on_scroll_down", false)! + ? () => control.triggerEvent("scroll_down") + : null, + onCopy: control.getBool("on_copy", false)! + ? () => control.triggerEvent("copy") + : null, + onCut: control.getBool("on_cut", false)! + ? () => control.triggerEvent("cut") + : null, + onPaste: control.getBool("on_paste", false)! + ? () => control.triggerEvent("paste") + : null, + onLongPress: control.getBool("on_dismiss", false)! + ? () => control.triggerEvent("dismiss") + : null, + onMoveCursorForwardByCharacter: control.getBool( + "on_move_cursor_forward_by_character", false)! + ? (bool value) => + control.triggerEvent("move_cursor_forward_by_character", value) + : null, + onMoveCursorBackwardByCharacter: control.getBool( + "on_move_cursor_backward_by_character", false)! + ? (bool value) => + control.triggerEvent("move_cursor_backward_by_character", value) + : null, + onDidGainAccessibilityFocus: + control.getBool("on_did_gain_accessibility_focus", false)! + ? () => control.triggerEvent("did_gain_accessibility_focus") + : null, + onDidLoseAccessibilityFocus: + control.getBool("on_did_lose_accessibility_focus", false)! + ? () => control.triggerEvent("did_lose_accessibility_focus") + : null, + onSetText: control.getBool("on_set_text", false)! + ? (String text) => control.triggerEvent("set_text", text) + : null, + ); - return constrainedControl(context, semantics, parent, control); + return ConstrainedControl(control: control, child: semantics); } } diff --git a/packages/flet/lib/src/controls/semantics_service.dart b/packages/flet/lib/src/controls/semantics_service.dart deleted file mode 100644 index 359864986..000000000 --- a/packages/flet/lib/src/controls/semantics_service.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/semantics.dart'; - -import '../flet_control_backend.dart'; -import '../models/control.dart'; -import '../utils/others.dart'; - -class SemanticsServiceControl extends StatelessWidget { - final Control? parent; - final Control control; - final FletControlBackend backend; - - const SemanticsServiceControl( - {super.key, this.parent, required this.control, required this.backend}); - - @override - Widget build(BuildContext context) { - debugPrint("SemanticsService build: ${control.id}"); - - backend.subscribeMethods(control.id, (methodName, args) async { - var message = args["message"].toString(); - switch (methodName) { - case "announce_message": - debugPrint("SemanticsService.announceMessage($message)"); - var rtl = args["rtl"] == "true"; - var assertiveness = parseAssertiveness( - args["assertiveness"].toString(), Assertiveness.polite)!; - SemanticsService.announce( - message, rtl ? TextDirection.rtl : TextDirection.ltr, - assertiveness: assertiveness); - break; - case "announce_tooltip": - debugPrint("SemanticsService.announceTooltip($message)"); - SemanticsService.tooltip(message); - break; - } - return null; - }); - - return const SizedBox.shrink(); - } -} diff --git a/packages/flet/lib/src/controls/shader_mask.dart b/packages/flet/lib/src/controls/shader_mask.dart index 16dec0618..8c9373f94 100644 --- a/packages/flet/lib/src/controls/shader_mask.dart +++ b/packages/flet/lib/src/controls/shader_mask.dart @@ -1,67 +1,38 @@ import 'package:flutter/material.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/borders.dart'; import '../utils/gradient.dart'; import '../utils/images.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; class ShaderMaskControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - const ShaderMaskControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive}); + const ShaderMaskControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("ShaderMask build: ${control.id}"); - - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - var blendMode = - parseBlendMode(control.attrString("blendMode"), BlendMode.modulate)!; - bool disabled = control.isDisabled || parentDisabled; - - var gradient = parseGradient(Theme.of(context), control, "shader"); + var gradient = control.getGradient("shader", Theme.of(context)); if (gradient == null) { return const ErrorControl("ShaderMask.shader must be provided"); } - - return constrainedControl( - context, - _clipCorners( - ShaderMask( - shaderCallback: (bounds) { - debugPrint("shaderCallback: $bounds, $gradient"); - return gradient.createShader(bounds); - }, - blendMode: blendMode, - child: contentCtrls.isNotEmpty - ? createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: parentAdaptive) - : null), - control), - parent, - control); + final shaderMask = ShaderMask( + shaderCallback: (bounds) => gradient.createShader(bounds), + blendMode: control.getBlendMode("blend_mode", BlendMode.modulate)!, + child: control.buildWidget("content")); + return ConstrainedControl( + control: control, + child: _clipCorners(shaderMask, + borderRadius: control.getBorderRadius("border_radius"))); } - Widget _clipCorners(Widget widget, Control control) { - var borderRadius = parseBorderRadius(control, "borderRadius"); + Widget _clipCorners(Widget widget, {BorderRadius? borderRadius}) { return borderRadius != null - ? ClipRRect( - borderRadius: borderRadius, - child: widget, - ) + ? ClipRRect(borderRadius: borderRadius, child: widget) : widget; } } diff --git a/packages/flet/lib/src/controls/shake_detector.dart b/packages/flet/lib/src/controls/shake_detector.dart deleted file mode 100644 index c3c4bfb1d..000000000 --- a/packages/flet/lib/src/controls/shake_detector.dart +++ /dev/null @@ -1,191 +0,0 @@ -import 'dart:async'; -import 'dart:math'; - -import 'package:flutter/widgets.dart'; -import 'package:sensors_plus/sensors_plus.dart'; - -import '../flet_control_backend.dart'; -import '../models/control.dart'; - -class ShakeDetectorControl extends StatefulWidget { - final Control? parent; - final Control control; - final Widget? nextChild; - final FletControlBackend backend; - - const ShakeDetectorControl( - {super.key, - required this.parent, - required this.control, - required this.nextChild, - required this.backend}); - - @override - State createState() => _ShakeDetectorControlState(); -} - -class _ShakeDetectorControlState extends State { - ShakeDetector? _shakeDetector; - int? _minimumShakeCount; - int? _shakeSlopTimeMs; - int? _shakeCountResetTimeMs; - double? _shakeThresholdGravity; - - @override - void dispose() { - _shakeDetector?.stopListening(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - debugPrint("ShakeDetector build: ${widget.control.id}"); - - var minimumShakeCount = widget.control.attrInt("minimumShakeCount", 1)!; - var shakeSlopTimeMs = widget.control.attrInt("shakeSlopTimeMs", 500)!; - var shakeCountResetTimeMs = - widget.control.attrInt("shakeCountResetTimeMs", 3000)!; - var shakeThresholdGravity = - widget.control.attrDouble("shakeThresholdGravity", 2.7)!; - - if (minimumShakeCount != _minimumShakeCount || - shakeSlopTimeMs != _shakeSlopTimeMs || - shakeCountResetTimeMs != _shakeCountResetTimeMs || - shakeThresholdGravity != _shakeThresholdGravity) { - _minimumShakeCount = minimumShakeCount; - _shakeSlopTimeMs = shakeSlopTimeMs; - _shakeCountResetTimeMs = shakeCountResetTimeMs; - _shakeThresholdGravity = shakeThresholdGravity; - - _shakeDetector?.stopListening(); - _shakeDetector = ShakeDetector.autoStart( - onPhoneShake: () { - widget.backend.triggerControlEvent(widget.control.id, "shake"); - }, - minimumShakeCount: minimumShakeCount, - shakeSlopTimeMS: shakeSlopTimeMs, - shakeCountResetTime: shakeCountResetTimeMs, - shakeThresholdGravity: shakeThresholdGravity, - ); - } - - return widget.nextChild ?? const SizedBox.shrink(); - } -} - -/* -Source: https://github.com/dieringe/shake/blob/master/lib/shake.dart - -Copyright 2019 Deven Joshi - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -/// Callback for phone shakes -typedef PhoneShakeCallback = void Function(); - -/// ShakeDetector class for phone shake functionality -class ShakeDetector { - /// User callback for phone shake - final PhoneShakeCallback onPhoneShake; - - /// Shake detection threshold - final double shakeThresholdGravity; - - /// Minimum time between shake - final int shakeSlopTimeMS; - - /// Time before shake count resets - final int shakeCountResetTime; - - /// Number of shakes required before shake is triggered - final int minimumShakeCount; - - int mShakeTimestamp = DateTime.now().millisecondsSinceEpoch; - int mShakeCount = 0; - - /// StreamSubscription for Accelerometer events - StreamSubscription? streamSubscription; - - /// This constructor waits until [startListening] is called - ShakeDetector.waitForStart({ - required this.onPhoneShake, - this.shakeThresholdGravity = 2.7, - this.shakeSlopTimeMS = 500, - this.shakeCountResetTime = 3000, - this.minimumShakeCount = 1, - }); - - /// This constructor automatically calls [startListening] and starts detection and callbacks. - ShakeDetector.autoStart({ - required this.onPhoneShake, - this.shakeThresholdGravity = 2.7, - this.shakeSlopTimeMS = 500, - this.shakeCountResetTime = 3000, - this.minimumShakeCount = 1, - }) { - startListening(); - } - - /// Starts listening to accelerometer events - void startListening() { - streamSubscription = accelerometerEventStream().listen( - (AccelerometerEvent event) { - double x = event.x; - double y = event.y; - double z = event.z; - - double gX = x / 9.80665; - double gY = y / 9.80665; - double gZ = z / 9.80665; - - // gForce will be close to 1 when there is no movement. - double gForce = sqrt(gX * gX + gY * gY + gZ * gZ); - - if (gForce > shakeThresholdGravity) { - var now = DateTime.now().millisecondsSinceEpoch; - // ignore shake events too close to each other (500ms) - if (mShakeTimestamp + shakeSlopTimeMS > now) { - return; - } - - // reset the shake count after 3 seconds of no shakes - if (mShakeTimestamp + shakeCountResetTime < now) { - mShakeCount = 0; - } - - mShakeTimestamp = now; - mShakeCount++; - - if (mShakeCount >= minimumShakeCount) { - onPhoneShake(); - } - } - }, - ); - } - - /// Stops listening to accelerometer events - void stopListening() { - streamSubscription?.cancel(); - } -} diff --git a/packages/flet/lib/src/controls/slider.dart b/packages/flet/lib/src/controls/slider.dart index b46934aaa..01d435f88 100644 --- a/packages/flet/lib/src/controls/slider.dart +++ b/packages/flet/lib/src/controls/slider.dart @@ -1,37 +1,26 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; import '../utils/colors.dart'; import '../utils/debouncer.dart'; import '../utils/edge_insets.dart'; +import '../utils/misc.dart'; import '../utils/mouse.dart'; -import '../utils/others.dart'; +import '../utils/numbers.dart'; import '../utils/platform.dart'; -import 'create_control.dart'; -import 'cupertino_slider.dart'; -import 'flet_store_mixin.dart'; +import 'base_controls.dart'; class SliderControl extends StatefulWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const SliderControl( - {super.key, - this.parent, - required this.control, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + SliderControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _SliderControlState(); } -class _SliderControlState extends State with FletStoreMixin { +class _SliderControlState extends State { double _value = 0; final _debouncer = Debouncer(milliseconds: isDesktopPlatform() ? 10 : 100); late final FocusNode _focusNode; @@ -52,97 +41,77 @@ class _SliderControlState extends State with FletStoreMixin { } void _onFocusChange() { - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); } void onChange(double value) { - var svalue = value.toString(); - debugPrint(svalue); _value = value; - var props = {"value": svalue}; - widget.backend.updateControlState(widget.control.id, props, server: false); + var props = {"value": value}; + widget.control.updateProperties(props, python: false, notify: true); _debouncer.run(() { - widget.backend.updateControlState(widget.control.id, props); - widget.backend.triggerControlEvent(widget.control.id, "change"); + widget.control.updateProperties(props, notify: true); + widget.control.triggerEvent("change"); }); } @override Widget build(BuildContext context) { debugPrint("SliderControl build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - return withPagePlatform((context, platform) { - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - if (adaptive == true && - (platform == TargetPlatform.iOS || - platform == TargetPlatform.macOS)) { - return CupertinoSliderControl( - control: widget.control, - parentDisabled: widget.parentDisabled, - backend: widget.backend); - } - - String? label = widget.control.attrString("label"); - double min = widget.control.attrDouble("min", 0)!; - double max = widget.control.attrDouble("max", 1)!; - int round = widget.control.attrInt("round", 0)!; + var label = widget.control.getString("label"); + var min = widget.control.getDouble("min", 0)!; + var max = widget.control.getDouble("max", 1)!; + var round = widget.control.getInt("round", 0)!; - double value = widget.control.attrDouble("value", min)!; - if (_value != value) { - // verify limits - if (value < min) { - _value = min; - } else if (value > max) { - _value = max; - } else { - _value = value; - } + double value = widget.control.getDouble("value", min)!; + if (_value != value) { + // verify limits + if (value < min) { + _value = min; + } else if (value > max) { + _value = max; + } else { + _value = value; } + } - var slider = Slider( - autofocus: widget.control.attrBool("autofocus", false)!, - focusNode: _focusNode, - value: _value, - min: min, - max: max, - year2023: widget.control.attrBool("year2023"), - divisions: widget.control.attrInt("divisions"), - label: label?.replaceAll("{value}", _value.toStringAsFixed(round)), - activeColor: widget.control.attrColor("activeColor", context), - inactiveColor: widget.control.attrColor("inactiveColor", context), - overlayColor: parseWidgetStateColor( - Theme.of(context), widget.control, "overlayColor"), - allowedInteraction: - parseSliderInteraction(widget.control.attrString("interaction")), - thumbColor: widget.control.attrColor("thumbColor", context), - padding: parseEdgeInsets(widget.control, "padding"), - onChanged: !disabled - ? (double value) { - onChange(value); - } - : null, - mouseCursor: - parseMouseCursor(widget.control.attrString("mouseCursor")), - secondaryActiveColor: - widget.control.attrColor("secondaryActiveColor", context), - secondaryTrackValue: widget.control.attrDouble("secondaryTrackValue"), - onChangeStart: !disabled - ? (double value) { - widget.backend.triggerControlEvent( - widget.control.id, "change_start", value.toString()); - } - : null, - onChangeEnd: !disabled - ? (double value) { - widget.backend.triggerControlEvent( - widget.control.id, "change_end", value.toString()); - } - : null); + var slider = Slider( + autofocus: widget.control.getBool("autofocus", false)!, + focusNode: _focusNode, + value: _value, + min: min, + max: max, + year2023: widget.control.getBool("year_2023"), + // todo: remove + divisions: widget.control.getInt("divisions"), + label: label?.replaceAll("{value}", _value.toStringAsFixed(round)), + activeColor: widget.control.getColor("active_color", context), + inactiveColor: widget.control.getColor("inactive_color", context), + overlayColor: widget.control + .getWidgetStateColor("overlay_color", Theme.of(context)), + allowedInteraction: widget.control.getSliderInteraction("interaction"), + thumbColor: widget.control.getColor("thumb_color", context), + padding: widget.control.getPadding("padding"), + onChanged: !widget.control.disabled + ? (double value) { + onChange(value); + } + : null, + mouseCursor: widget.control.getMouseCursor("mouse_cursor"), + secondaryActiveColor: + widget.control.getColor("secondary_active_color", context), + secondaryTrackValue: widget.control.getDouble("secondary_track_value"), + onChangeStart: !widget.control.disabled + ? (double value) { + widget.control.triggerEvent("change_start", value); + } + : null, + onChangeEnd: !widget.control.disabled + ? (double value) { + widget.control.triggerEvent("change_end", value); + } + : null); - return constrainedControl(context, slider, widget.parent, widget.control); - }); + return ConstrainedControl(control: widget.control, child: slider); } } diff --git a/packages/flet/lib/src/controls/snack_bar.dart b/packages/flet/lib/src/controls/snack_bar.dart index d545d3977..4496df4e8 100644 --- a/packages/flet/lib/src/controls/snack_bar.dart +++ b/packages/flet/lib/src/controls/snack_bar.dart @@ -1,68 +1,79 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; +import '../flet_backend.dart'; import '../models/control.dart'; import '../utils/borders.dart'; +import '../utils/colors.dart'; import '../utils/dismissible.dart'; import '../utils/edge_insets.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'error.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; +import '../utils/time.dart'; +import '../widgets/error.dart'; class SnackBarControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final Widget? nextChild; - final FletControlBackend backend; - - const SnackBarControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.nextChild, - required this.backend}); + + SnackBarControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _SnackBarControlState(); } class _SnackBarControlState extends State { + Widget? _dialog; bool _open = false; + bool _dismissed = false; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _toggleSnackBar(); + } - Widget _createSnackBar() { - bool disabled = widget.control.isDisabled || widget.parentDisabled; - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); + @override + void didUpdateWidget(covariant SnackBarControl oldWidget) { + super.didUpdateWidget(oldWidget); + _toggleSnackBar(); + } - if (contentCtrls.isEmpty) { + Widget _createSnackBar(FletBackend backend) { + var content = widget.control.buildTextOrWidget("content"); + if (content == null) { return const ErrorControl( "SnackBar.content must be provided and visible"); } - var actionName = widget.control.attrString("action", "")!; - SnackBarAction? action = actionName != "" - ? SnackBarAction( - label: actionName, - textColor: widget.control.attrColor("actionColor", context), - onPressed: () { - debugPrint("SnackBar ${widget.control.id} clicked!"); - widget.backend.triggerControlEvent(widget.control.id, "action"); - }) - : null; - - SnackBarBehavior? behavior = - parseSnackBarBehavior(widget.control.attrString("behavior")); + final actionControl = widget.control.child("action"); + SnackBarAction? action; + if (actionControl != null) { + action = SnackBarAction( + label: actionControl.getString("label", "Action")!, + backgroundColor: actionControl.getColor("bgcolor", context), + textColor: actionControl.getColor("text_color", context), + disabledBackgroundColor: + actionControl.getColor("disabled_bgcolor", context), + disabledTextColor: + actionControl.getColor("disabled_text_color", context), + onPressed: () => actionControl.triggerEvent("click"), + ); + } else { + var label = widget.control.getString("action"); + action = label != null + ? SnackBarAction( + label: label, + onPressed: () {}, + ) + : null; + } - var width = widget.control.attrDouble("width"); - var margin = parseEdgeInsets(widget.control, "margin"); + var width = widget.control.getDouble("width"); + var margin = widget.control.getMargin("margin"); // if behavior is not floating, ignore margin and width + SnackBarBehavior? behavior = widget.control.getSnackBarBehavior("behavior"); if (behavior != SnackBarBehavior.floating) { margin = null; width = null; @@ -72,62 +83,75 @@ class _SnackBarControlState extends State { margin = (width != null && margin != null) ? null : margin; return SnackBar( - behavior: behavior, - clipBehavior: parseClip( - widget.control.attrString("clipBehavior"), Clip.hardEdge)!, - actionOverflowThreshold: - widget.control.attrDouble("actionOverflowThreshold"), - shape: parseOutlinedBorder(widget.control, "shape"), - onVisible: () { - debugPrint("SnackBar.onVisible(${widget.control.id})"); - widget.backend.triggerControlEvent(widget.control.id, "visible"); - }, - dismissDirection: parseDismissDirection( - widget.control.attrString("dismissDirection")), - showCloseIcon: widget.control.attrBool("showCloseIcon"), - closeIconColor: widget.control.attrColor("closeIconColor", context), - content: createControl(widget.control, contentCtrls.first.id, disabled, - parentAdaptive: widget.parentAdaptive), - backgroundColor: widget.control.attrColor("bgColor", context), - action: action, - margin: margin, - padding: parseEdgeInsets(widget.control, "padding"), - width: width, - elevation: widget.control.attrDouble("elevation"), - duration: - Duration(milliseconds: widget.control.attrInt("duration", 4000)!)); + behavior: behavior, + clipBehavior: + parseClip(widget.control.getString("clip_behavior"), Clip.hardEdge)!, + actionOverflowThreshold: + widget.control.getDouble("action_overflow_threshold"), + shape: widget.control.getOutlinedBorder("shape", Theme.of(context)), + onVisible: () { + backend.triggerControlEvent(widget.control, "visible"); + }, + dismissDirection: + parseDismissDirection(widget.control.getString("dismiss_direction")), + showCloseIcon: widget.control.getBool("show_close_icon"), + closeIconColor: widget.control.getColor("close_icon_color", context), + content: content, + backgroundColor: widget.control.getColor("bgcolor", context), + action: action, + margin: margin, + padding: widget.control.getPadding("padding"), + width: width, + elevation: widget.control.getDouble("elevation"), + duration: widget.control + .getDuration("duration", const Duration(milliseconds: 4000))!, + ); } - @override - Widget build(BuildContext context) { - debugPrint("SnackBar build: ${widget.control.id}"); - - var open = widget.control.attrBool("open", false)!; - var removeCurrentSnackbar = true; + void _toggleSnackBar() { + if (_dismissed) return; - //widget.control.attrBool("removeCurrentSnackBar", false)!; + debugPrint("SnackBar build: ${widget.control.id}"); - debugPrint("Current open state: $_open"); - debugPrint("New open state: $open"); + var open = widget.control.getBool("open", false)!; if (open && (open != _open)) { - var snackBar = _createSnackBar(); - if (snackBar is ErrorControl) { - return snackBar; + _dialog = _createSnackBar(FletBackend.of(context)); + + if (_dialog is ErrorControl) { + debugPrint( + "SnackBar: ErrorControl, not showing dialog: ${(_dialog as ErrorControl).message}"); + return; } - WidgetsBinding.instance.addPostFrameCallback((_) { - if (removeCurrentSnackbar) { - ScaffoldMessenger.of(context).removeCurrentSnackBar(); - } - ScaffoldMessenger.of(context).showSnackBar(snackBar as SnackBar); + _open = open; - widget.backend.updateControlState(widget.control.id, {"open": "false"}); + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).removeCurrentSnackBar(); + ScaffoldMessenger.of(context) + .showSnackBar(_dialog as SnackBar) + .closed + .then((reason) { + if (!_dismissed) { + _dismissed = true; + debugPrint( + "Dismissing SnackBar(${widget.control.id}) with reason: $reason"); + _open = false; + widget.control.updateProperties({"open": false}); + widget.control.triggerEvent("dismiss"); + } + }); + }); + } else if (!open && _open) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).removeCurrentSnackBar(); + _open = false; }); } + } - _open = open; - - return widget.nextChild ?? const SizedBox.shrink(); + @override + Widget build(BuildContext context) { + return _dialog is ErrorControl ? _dialog! : const SizedBox.shrink(); } } diff --git a/packages/flet/lib/src/controls/stack.dart b/packages/flet/lib/src/controls/stack.dart index f72d5a6e9..33e6aab1b 100644 --- a/packages/flet/lib/src/controls/stack.dart +++ b/packages/flet/lib/src/controls/stack.dart @@ -1,51 +1,31 @@ import 'package:flutter/widgets.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/alignment.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; class StackControl extends StatelessWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final bool? parentAdaptive; - final List children; - const StackControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive}); + const StackControl({ + super.key, + required this.control, + }); @override Widget build(BuildContext context) { debugPrint("Stack build: ${control.id}"); - bool disabled = control.isDisabled || parentDisabled; - bool? adaptive = control.attrBool("adaptive") ?? parentAdaptive; - - var clipBehavior = - parseClip(control.attrString("clipBehavior"), Clip.hardEdge)!; - - StackFit fit = parseStackFit(control.attrString("fit"), StackFit.loose)!; - var ctrls = children - .where((c) => c.isVisible) - .map((c) => - createControl(control, c.id, disabled, parentAdaptive: adaptive)) - .toList(); - - return constrainedControl( - context, - Stack( - clipBehavior: clipBehavior, - fit: fit, - alignment: parseAlignment(control, "alignment") ?? - AlignmentDirectional.topStart, - children: ctrls, - ), - parent, - control); + final stack = Stack( + clipBehavior: + parseClip(control.getString("clipBehavior"), Clip.hardEdge)!, + fit: parseStackFit(control.getString("fit"), StackFit.loose)!, + alignment: + control.getAlignment("alignment") ?? AlignmentDirectional.topStart, + children: control.buildWidgets("controls"), + ); + return ConstrainedControl(control: control, child: stack); } } diff --git a/packages/flet/lib/src/controls/submenu_button.dart b/packages/flet/lib/src/controls/submenu_button.dart index 13648027b..5ab0c57f1 100644 --- a/packages/flet/lib/src/controls/submenu_button.dart +++ b/packages/flet/lib/src/controls/submenu_button.dart @@ -1,33 +1,25 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; import '../utils/buttons.dart'; import '../utils/menu.dart'; -import '../utils/others.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; import '../utils/transforms.dart'; -import 'create_control.dart'; +import 'base_controls.dart'; -class SubMenuButtonControl extends StatefulWidget { - final Control? parent; +class SubmenuButtonControl extends StatefulWidget { final Control control; - final List children; - final bool parentDisabled; - final FletControlBackend backend; - const SubMenuButtonControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.backend}); + SubmenuButtonControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override - State createState() => _SubMenuButtonControlState(); + State createState() => _SubmenuButtonControlState(); } -class _SubMenuButtonControlState extends State { +class _SubmenuButtonControlState extends State { late final FocusNode _focusNode; String? _lastFocusValue; @@ -46,26 +38,15 @@ class _SubMenuButtonControlState extends State { } void _onFocusChange() { - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); } @override Widget build(BuildContext context) { debugPrint("SubMenuButton build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - var content = - widget.children.where((c) => c.name == "content" && c.isVisible); - var ctrls = - widget.children.where((c) => c.name == "controls" && c.isVisible); - var leading = - widget.children.where((c) => c.name == "leading" && c.isVisible); - var trailing = - widget.children.where((c) => c.name == "trailing" && c.isVisible); var theme = Theme.of(context); - var style = parseButtonStyle(Theme.of(context), widget.control, "style", + var style = widget.control.getButtonStyle("style", Theme.of(context), defaultForegroundColor: theme.colorScheme.primary, defaultBackgroundColor: Colors.transparent, defaultOverlayColor: Colors.transparent, @@ -78,53 +59,38 @@ class _SubMenuButtonControlState extends State { ? const StadiumBorder() : RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))); - bool onOpen = widget.control.attrBool("onOpen", false)!; - bool onClose = widget.control.attrBool("onClose", false)!; - bool onHover = widget.control.attrBool("onHover", false)!; + bool onOpen = widget.control.getBool("on_open", false)!; + bool onClose = widget.control.getBool("on_close", false)!; + bool onHover = widget.control.getBool("on_hover", false)!; - var subMenu = SubmenuButton( + var subMenuButton = SubmenuButton( focusNode: _focusNode, clipBehavior: - parseClip(widget.control.attrString("clipBehavior"), Clip.hardEdge)!, + widget.control.getClipBehavior("clip_behavior", Clip.hardEdge)!, style: style, - menuStyle: parseMenuStyle(Theme.of(context), widget.control, "menuStyle"), - alignmentOffset: parseOffset(widget.control, "alignmentOffset"), - onClose: onClose && !disabled - ? () { - widget.backend.triggerControlEvent(widget.control.id, "close"); - } - : null, - onHover: onHover && !disabled - ? (bool value) { - widget.backend - .triggerControlEvent(widget.control.id, "hover", "$value"); - } - : null, - onOpen: onOpen && !disabled - ? () { - widget.backend.triggerControlEvent(widget.control.id, "open"); - } - : null, - leadingIcon: leading.isNotEmpty - ? createControl(widget.control, leading.first.id, disabled) + menuStyle: widget.control.getMenuStyle("menu_style", Theme.of(context)), + alignmentOffset: widget.control.getOffset("alignment_offset"), + onClose: onClose && !widget.control.disabled + ? () => widget.control.triggerEvent("close") : null, - trailingIcon: trailing.isNotEmpty - ? createControl(widget.control, trailing.first.id, disabled) + onHover: onHover && !widget.control.disabled + ? (bool value) => widget.control.triggerEvent("hover", value) : null, - menuChildren: ctrls.map((c) { - return createControl(widget.control, c.id, disabled); - }).toList(), - child: content.isNotEmpty - ? createControl(widget.control, content.first.id, disabled) + onOpen: onOpen && !widget.control.disabled + ? () => widget.control.triggerEvent("open") : null, + leadingIcon: widget.control.buildWidget("leading"), + trailingIcon: widget.control.buildWidget("trailing"), + menuChildren: widget.control.buildWidgets("controls"), + child: widget.control.buildWidget("content"), ); - var focusValue = widget.control.attrString("focus"); + var focusValue = widget.control.getString("focus"); if (focusValue != null && focusValue != _lastFocusValue) { _lastFocusValue = focusValue; _focusNode.requestFocus(); } - return constrainedControl(context, subMenu, widget.parent, widget.control); + return ConstrainedControl(control: widget.control, child: subMenuButton); } } diff --git a/packages/flet/lib/src/controls/switch.dart b/packages/flet/lib/src/controls/switch.dart index 9dd25e4af..a0ad92780 100644 --- a/packages/flet/lib/src/controls/switch.dart +++ b/packages/flet/lib/src/controls/switch.dart @@ -1,41 +1,27 @@ -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; import '../utils/colors.dart'; import '../utils/icons.dart'; +import '../utils/misc.dart'; import '../utils/mouse.dart'; import '../utils/numbers.dart'; -import '../utils/others.dart'; import '../utils/text.dart'; -import 'create_control.dart'; -import 'cupertino_switch.dart'; -import 'flet_store_mixin.dart'; +import 'base_controls.dart'; +import 'control_widget.dart'; import 'list_tile.dart'; class SwitchControl extends StatefulWidget { - final Control? parent; final Control control; - final bool parentDisabled; - final bool? parentAdaptive; - final List children; - final FletControlBackend backend; - - const SwitchControl( - {super.key, - this.parent, - required this.control, - required this.parentDisabled, - required this.parentAdaptive, - required this.children, - required this.backend}); + + SwitchControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _SwitchControlState(); } -class _SwitchControlState extends State with FletStoreMixin { +class _SwitchControlState extends State { bool _value = false; late final FocusNode _focusNode; @@ -54,138 +40,118 @@ class _SwitchControlState extends State with FletStoreMixin { } void _onChange(bool value) { - var svalue = value.toString(); - debugPrint(svalue); _value = value; - widget.backend.updateControlState(widget.control.id, {"value": svalue}); - widget.backend.triggerControlEvent(widget.control.id, "change", svalue); + var props = {"value": value}; + widget.control.updateProperties(props, notify: true); + widget.control.triggerEvent("change"); } void _onFocusChange() { - widget.backend.triggerControlEvent( - widget.control.id, - _focusNode.hasFocus ? "focus" : "blur", - _focusNode.hasPrimaryFocus.toString()); + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); } @override Widget build(BuildContext context) { debugPrint("SwitchControl build: ${widget.control.id}"); - return withPagePlatform((context, platform) { - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - if (adaptive == true && - (platform == TargetPlatform.iOS || - platform == TargetPlatform.macOS)) { - return CupertinoSwitchControl( - control: widget.control, - parentDisabled: widget.parentDisabled, - backend: widget.backend); - } - - var label = widget.children.firstWhereOrNull((c) => c.isVisible); - String labelStr = widget.control.attrString("label", "")!; - LabelPosition labelPosition = parseLabelPosition( - widget.control.attrString("labelPosition"), LabelPosition.right)!; - double? width = widget.control.attrDouble("width"); - double? height = widget.control.attrDouble("height"); - bool autofocus = widget.control.attrBool("autofocus", false)!; - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - TextStyle? labelStyle = - parseTextStyle(Theme.of(context), widget.control, "labelStyle"); - if (disabled && labelStyle != null) { - labelStyle = labelStyle.apply(color: Theme.of(context).disabledColor); - } + var label = widget.control.get("label"); + LabelPosition labelPosition = + widget.control.getLabelPosition("label_position", LabelPosition.right)!; + double? width = widget.control.getDouble("width"); + double? height = widget.control.getDouble("height"); + bool autofocus = widget.control.getBool("autofocus", false)!; + + TextStyle? labelStyle = + widget.control.getTextStyle("label_style", Theme.of(context)); + if (widget.control.disabled && labelStyle != null) { + labelStyle = labelStyle.apply(color: Theme.of(context).disabledColor); + } + + bool value = widget.control.getBool("value", false)!; + if (_value != value) { + _value = value; + } + ThemeData theme = Theme.of(context); + + var s = Switch( + autofocus: autofocus, + focusNode: _focusNode, + activeColor: widget.control.getColor("active_color", context), + activeTrackColor: + widget.control.getColor("active_track_color", context), + inactiveThumbColor: + widget.control.getColor("inactive_thumb_color", context), + inactiveTrackColor: + widget.control.getColor("inactive_track_color", context), + thumbColor: widget.control.getWidgetStateColor("thumb_color", theme), + thumbIcon: widget.control.getWidgetStateIcon("thumb_icon", theme), + trackColor: widget.control.getWidgetStateColor("track_color", theme), + focusColor: widget.control.getColor("focus_color", context), + value: _value, + mouseCursor: widget.control.getMouseCursor("mouse_cursor"), + splashRadius: widget.control.getDouble("splash_radius"), + hoverColor: widget.control.getColor("hover_color", context), + overlayColor: + widget.control.getWidgetStateColor("overlay_color", theme), + trackOutlineColor: + widget.control.getWidgetStateColor("track_outline_color", theme), + trackOutlineWidth: + widget.control.getWidgetStateDouble("track_outline_width"), + onChanged: !widget.control.disabled + ? (bool value) { + _onChange(value); + } + : null); + + ListTileClicks.of(context)?.notifier.addListener(() { + _onChange(!_value); + }); - bool value = widget.control.attrBool("value", false)!; - if (_value != value) { - _value = value; + Widget result = s; + + if (label is Control || (label is String)) { + Widget? labelWidget; + if (label is Control) { + labelWidget = ControlWidget(control: label); + } else { + labelWidget = widget.control.disabled + ? Text(label, style: labelStyle) + : MouseRegion( + cursor: SystemMouseCursors.click, + child: Text(label, style: labelStyle)); } - var s = Switch( - autofocus: autofocus, - focusNode: _focusNode, - activeColor: widget.control.attrColor("activeColor", context), - activeTrackColor: - widget.control.attrColor("activeTrackColor", context), - inactiveThumbColor: - widget.control.attrColor("inactiveThumbColor", context), - inactiveTrackColor: - widget.control.attrColor("inactiveTrackColor", context), - thumbColor: parseWidgetStateColor( - Theme.of(context), widget.control, "thumbColor"), - thumbIcon: parseWidgetStateIcon( - Theme.of(context), widget.control, "thumbIcon"), - trackColor: parseWidgetStateColor( - Theme.of(context), widget.control, "trackColor"), - focusColor: widget.control.attrColor("focusColor", context), - value: _value, - mouseCursor: - parseMouseCursor(widget.control.attrString("mouseCursor")), - splashRadius: widget.control.attrDouble("splashRadius"), - hoverColor: widget.control.attrColor("hoverColor", context), - overlayColor: parseWidgetStateColor( - Theme.of(context), widget.control, "overlayColor"), - trackOutlineColor: parseWidgetStateColor( - Theme.of(context), widget.control, "trackOutlineColor"), - trackOutlineWidth: - parseWidgetStateDouble(widget.control, "trackOutlineWidth"), - onChanged: !disabled - ? (bool value) { - _onChange(value); + result = MergeSemantics( + child: GestureDetector( + onTap: !widget.control.disabled + ? () { + _onChange(!_value); } - : null); - - ListTileClicks.of(context)?.notifier.addListener(() { - _onChange(!_value); - }); - - Widget result = s; - if (label != null || (labelStr != "")) { - Widget? labelWidget; - if (label != null) { - labelWidget = createControl(widget.control, label.id, disabled); - } else { - labelWidget = disabled - ? Text(labelStr, style: labelStyle) - : MouseRegion( - cursor: SystemMouseCursors.click, - child: Text(labelStr, style: labelStyle)); - } - - result = MergeSemantics( - child: GestureDetector( - onTap: !disabled - ? () { - _onChange(!_value); - } - : null, - child: labelPosition == LabelPosition.right - ? Row( - // mainAxisSize: MainAxisSize.min, - children: [result, labelWidget], - ) - : Row( - mainAxisSize: MainAxisSize.min, - children: [labelWidget, result], - ), - ), - ); - } - if (width != null || height != null) { - result = SizedBox( - width: width, - height: height, - child: FittedBox( - fit: BoxFit.fill, - child: result, - ), - ); - } - - return constrainedControl(context, result, widget.parent, widget.control); - }); + : null, + child: labelPosition == LabelPosition.right + ? Row( + // mainAxisSize: MainAxisSize.min, + children: [result, labelWidget], + ) + : Row( + mainAxisSize: MainAxisSize.min, + children: [labelWidget, result], + ), + ), + ); + } + if (width != null || height != null) { + result = SizedBox( + width: width, + height: height, + child: FittedBox( + fit: BoxFit.fill, + child: result, + ), + ); + } + + return ConstrainedControl(control: widget.control, child: result); + // }); } } diff --git a/packages/flet/lib/src/controls/tabs.dart b/packages/flet/lib/src/controls/tabs.dart index ba1752f25..8ca4dce03 100644 --- a/packages/flet/lib/src/controls/tabs.dart +++ b/packages/flet/lib/src/controls/tabs.dart @@ -1,39 +1,11 @@ -import 'dart:convert'; - +import 'package:flet/flet.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_redux/flutter_redux.dart'; - -import '../flet_control_backend.dart'; -import '../models/app_state.dart'; -import '../models/control.dart'; -import '../models/controls_view_model.dart'; -import '../utils/alignment.dart'; -import '../utils/borders.dart'; -import '../utils/colors.dart'; -import '../utils/edge_insets.dart'; -import '../utils/icons.dart'; -import '../utils/material_state.dart'; -import '../utils/mouse.dart'; -import '../utils/others.dart'; -import '../utils/text.dart'; -import 'create_control.dart'; class TabsControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const TabsControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + TabsControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _TabsControlState(); @@ -45,8 +17,23 @@ class _TabsControlState extends State TabController? _tabController; int _selectedIndex = 0; + @override + void didChangeDependencies() { + super.didChangeDependencies(); + debugPrint("Tabs.didChangeDependencies: ${widget.control.id}"); + _configureTabController(); + } + + @override + void didUpdateWidget(covariant TabsControl oldWidget) { + debugPrint("Tabs.didUpdateWidget: ${widget.control.id}"); + super.didUpdateWidget(oldWidget); + _configureTabController(); + } + @override void dispose() { + debugPrint("Tabs.dispose: ${widget.control.id}"); _tabController?.removeListener(_tabChanged); _tabController?.dispose(); super.dispose(); @@ -58,258 +45,212 @@ class _TabsControlState extends State } var index = _tabController!.index; if (_selectedIndex != index) { - debugPrint("Selected index: $index"); - widget.backend.updateControlState( - widget.control.id, {"selectedindex": index.toString()}); - widget.backend - .triggerControlEvent(widget.control.id, "change", index.toString()); + widget.control.updateProperties({"selected_index": index}); + widget.control.triggerEvent("change", index); _selectedIndex = index; } } - @override - Widget build(BuildContext context) { - debugPrint("TabsControl build: ${widget.control.id}"); - bool disabled = widget.control.isDisabled || widget.parentDisabled; - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - - // keep only visible tabs - widget.children.retainWhere((c) => c.isVisible); - - var tabs = StoreConnector( - distinct: true, - converter: (store) => ControlsViewModel.fromStore( - store, widget.children.map((c) => c.id)), - builder: (content, viewModel) { - var tabsSnapshot = - viewModel.controlViews.map((c) => c.control.id).join(); - if (tabsSnapshot != _tabsSnapshot) { - _tabsSnapshot = tabsSnapshot; + void _configureTabController() { + var tabs = widget.control.children("tabs"); + var tabsSnapshot = tabs.map((tab) => tab.id.toString()).join(); + if (tabsSnapshot != _tabsSnapshot) { + _tabsSnapshot = tabsSnapshot; - if (_tabController != null) { - _tabController!.removeListener(_tabChanged); - _tabController!.dispose(); - } - _tabController = TabController( - length: viewModel.controlViews.length, - animationDuration: Duration( - milliseconds: - widget.control.attrInt("animationDuration", 50)!), - vsync: this); - _tabController!.addListener(_tabChanged); - } + if (_tabController != null) { + _tabController!.removeListener(_tabChanged); + _tabController!.dispose(); + } + _selectedIndex = 0; + _tabController = TabController( + initialIndex: _selectedIndex, + length: tabs.length, + animationDuration: widget.control.getDuration( + "animation_duration", const Duration(milliseconds: 50))!, + vsync: this); + _tabController!.addListener(_tabChanged); + } - var selectedIndex = widget.control.attrInt("selectedIndex", 0)!; + var selectedIndex = widget.control.getInt("selected_index", 0)!; - if (selectedIndex > -1 && - selectedIndex < _tabController!.length && - _selectedIndex != selectedIndex) { - _selectedIndex = selectedIndex; - _tabController!.index = selectedIndex; - } + debugPrint("selectedIndex: $selectedIndex"); - // check if all tabs have no content - bool emptyTabs = !viewModel.controlViews.any( - (t) => t.children.any((c) => c.name == "content" && c.isVisible)); + if (selectedIndex > -1 && + selectedIndex < _tabController!.length && + _selectedIndex != selectedIndex) { + _selectedIndex = selectedIndex; + _tabController!.index = selectedIndex; + } + } - var overlayColorStr = widget.control.attrString("overlayColor"); - dynamic overlayColor; - if (overlayColorStr != null) { - overlayColor = getWidgetStateProperty( - json.decode(overlayColorStr), - (jv) => parseColor(Theme.of(context), jv as String)) ?? - TabBarTheme.of(context).overlayColor; - } + @override + Widget build(BuildContext context) { + debugPrint("TabsControl build: ${widget.control.id}"); - var indicatorBorderRadius = - parseBorderRadius(widget.control, "indicatorBorderRadius"); - var indicatorBorderSide = parseBorderSide( - Theme.of(context), widget.control, "indicatorBorderSide"); - var indicatorPadding = - parseEdgeInsets(widget.control, "indicatorPadding"); + // check if all tabs have no content + bool emptyTabs = !widget.control + .children("tabs") + .any((tab) => tab.child("content") != null); - var indicatorColor = - widget.control.attrColor("indicatorColor", context) ?? - TabBarTheme.of(context).indicatorColor ?? - Theme.of(context).colorScheme.primary; - var labelColor = widget.control.attrColor("labelColor", context) ?? - TabBarTheme.of(context).labelColor ?? - Theme.of(context).colorScheme.primary; - var unselectedLabelColor = - widget.control.attrColor("unselectedLabelColor", context) ?? - TabBarTheme.of(context).unselectedLabelColor ?? - Theme.of(context).colorScheme.onSurface; - var dividerColor = - widget.control.attrColor("dividerColor", context) ?? - TabBarTheme.of(context).dividerColor; + var overlayColor = widget.control.getWidgetStateColor( + "overlay_color", Theme.of(context), + defaultValue: TabBarTheme.of(context).overlayColor); + var indicatorBorderRadius = + widget.control.getBorderRadius("indicator_border_radius"); + var indicatorBorderSide = widget.control + .getBorderSide("indicator_border_side", Theme.of(context)); + var indicatorPadding = + widget.control.getPadding("indicator_padding", EdgeInsets.zero)!; + var indicatorColor = widget.control.getColor( + "indicator_color", + context, + TabBarTheme.of(context).indicatorColor ?? + Theme.of(context).colorScheme.primary)!; + var labelColor = widget.control.getColor( + "label_color", + context, + TabBarTheme.of(context).labelColor ?? + Theme.of(context).colorScheme.primary); + var unselectedLabelColor = widget.control.getColor( + "unselected_label_color", + context, + TabBarTheme.of(context).unselectedLabelColor ?? + Theme.of(context).colorScheme.onSurface); + var dividerColor = widget.control.getColor("divider_color", context) ?? + TabBarTheme.of(context).dividerColor; + var themeIndicator = + TabBarTheme.of(context).indicator as UnderlineTabIndicator?; + var indicatorTabSize = widget.control.getBool("indicator_tab_size"); + var isScrollable = widget.control.getBool("scrollable", true)!; + var secondary = widget.control.getBool("is_secondary", false)!; + var dividerHeight = widget.control.getDouble("divider_height"); + var enableFeedback = widget.control.getBool("enable_feedback"); + var indicatorWeight = widget.control.getDouble("indicator_thickness", 2.0)!; + var tabAlignment = parseTabAlignment( + widget.control.getString("tab_alignment"), + isScrollable ? TabAlignment.start : TabAlignment.fill)!; + var mouseCursor = + parseMouseCursor(widget.control.getString("mouse_cursor")); + var clipBehavior = + widget.control.getClipBehavior("clip_behavior", Clip.hardEdge)!; + var padding = parseEdgeInsets(widget.control.getPadding("padding")); + var labelPadding = widget.control.getPadding("label_padding"); + var labelStyle = + widget.control.getTextStyle("label_text_style", Theme.of(context)); + var unselectedLabelStyle = widget.control + .getTextStyle("unselected_label_text_style", Theme.of(context)); + var splashBorderRadius = + widget.control.getBorderRadius("splash_border_radius"); - var themeIndicator = - TabBarTheme.of(context).indicator as UnderlineTabIndicator?; - var indicatorTabSize = widget.control.attrBool("indicatorTabSize"); - var isScrollable = widget.control.attrBool("scrollable", true)!; - var secondary = widget.control.attrBool("isSecondary", false)!; - var dividerHeight = widget.control.attrDouble("dividerHeight"); - var enableFeedback = widget.control.attrBool("enableFeedback"); - var indicatorWeight = - widget.control.attrDouble("indicatorThickness", 2.0)!; - var tabAlignment = parseTabAlignment( - widget.control.attrString("tabAlignment"), - isScrollable ? TabAlignment.start : TabAlignment.fill)!; - var mouseCursor = - parseMouseCursor(widget.control.attrString("mouseCursor")); - var clipBehavior = parseClip( - widget.control.attrString("clipBehavior"), Clip.hardEdge)!; - var padding = parseEdgeInsets(widget.control, "padding"); - var labelPadding = parseEdgeInsets(widget.control, "labelPadding"); - var labelStyle = parseTextStyle( - Theme.of(context), widget.control, "labelTextStyle"); - var unselectedLabelStyle = parseTextStyle( - Theme.of(context), widget.control, "unselectedLabelTextStyle"); - var splashBorderRadius = - parseBorderRadius(widget.control, "splashBorderRadius"); + void onTap(int index) { + widget.control.triggerEvent("click", index); + } - void onTap(int index) { - widget.backend.triggerControlEvent( - widget.control.id, "click", index.toString()); - } + var indicator = indicatorBorderRadius != null || + indicatorBorderSide != null || + indicatorPadding != null + ? UnderlineTabIndicator( + borderRadius: indicatorBorderRadius ?? + themeIndicator?.borderRadius ?? + const BorderRadius.only( + topLeft: Radius.circular(2), topRight: Radius.circular(2)), + borderSide: indicatorBorderSide ?? + themeIndicator?.borderSide ?? + BorderSide( + width: themeIndicator?.borderSide.width ?? 2, + color: themeIndicator?.borderSide.color ?? indicatorColor), + insets: + indicatorPadding ?? themeIndicator?.insets ?? EdgeInsets.zero) + : TabBarTheme.of(context).indicator; + var indicatorSize = indicatorTabSize != null + ? (indicatorTabSize + ? TabBarIndicatorSize.tab + : TabBarIndicatorSize.label) + : TabBarTheme.of(context).indicatorSize; - var indicator = indicatorBorderRadius != null || - indicatorBorderSide != null || - indicatorPadding != null - ? UnderlineTabIndicator( - borderRadius: indicatorBorderRadius ?? - themeIndicator?.borderRadius ?? - const BorderRadius.only( - topLeft: Radius.circular(2), - topRight: Radius.circular(2)), - borderSide: indicatorBorderSide ?? - themeIndicator?.borderSide ?? - BorderSide( - width: themeIndicator?.borderSide.width ?? 2, - color: themeIndicator?.borderSide.color ?? - indicatorColor), - insets: indicatorPadding ?? - themeIndicator?.insets ?? - EdgeInsets.zero) - : TabBarTheme.of(context).indicator; - var indicatorSize = indicatorTabSize != null - ? (indicatorTabSize - ? TabBarIndicatorSize.tab - : TabBarIndicatorSize.label) - : TabBarTheme.of(context).indicatorSize; + var tabs = widget.control.children("tabs").map((tab) { + tab.notifyParent = true; + var icon = tab.buildIconOrWidget("icon"); + var label = tab.buildTextOrWidget("label"); - var tabs = viewModel.controlViews.map((tabView) { - var iconString = parseIcon(tabView.control.attrString("icon")); - var iconCtrls = tabView.children - .where((c) => c.name == "icon" && c.isVisible); + return Tab( + icon: icon, + height: tab.getDouble("height"), + iconMargin: tab.getMargin("icon_margin"), + child: label, + ); + }).toList(); - var icon = iconCtrls.isNotEmpty - ? createControl( - widget.control, iconCtrls.first.id, disabled, - parentAdaptive: adaptive) - : iconString != null - ? Icon(iconString) - : null; - var tabContentCtrls = tabView.children - .where((c) => c.name == "tab_content" && c.isVisible); + TabBar? tabBar; - if (tabContentCtrls.isNotEmpty) { - return Tab( - child: createControl( - widget.control, tabContentCtrls.first.id, disabled, - parentAdaptive: adaptive)); - } else { - return Tab( - text: tabView.control - .attrString("text", icon == null ? "" : null), - icon: icon, - height: tabView.control.attrDouble("height"), - iconMargin: parseEdgeInsets(tabView.control, "iconMargin"), - ); - } - }).toList(); + if (secondary) { + tabBar = TabBar.secondary( + tabAlignment: tabAlignment, + controller: _tabController, + isScrollable: isScrollable, + dividerHeight: dividerHeight, + enableFeedback: enableFeedback, + mouseCursor: mouseCursor, + indicatorWeight: indicatorWeight, + dividerColor: dividerColor, + indicatorSize: indicatorSize, + indicator: indicator, + indicatorColor: indicatorColor, + labelColor: labelColor, + unselectedLabelColor: unselectedLabelColor, + overlayColor: overlayColor, + tabs: tabs, + padding: padding, + labelPadding: labelPadding, + labelStyle: labelStyle, + unselectedLabelStyle: unselectedLabelStyle, + splashBorderRadius: splashBorderRadius, + indicatorPadding: indicatorPadding, + onTap: onTap); + } else { + tabBar = TabBar( + tabAlignment: tabAlignment, + controller: _tabController, + isScrollable: isScrollable, + dividerHeight: dividerHeight, + enableFeedback: enableFeedback, + mouseCursor: mouseCursor, + indicatorWeight: indicatorWeight, + dividerColor: dividerColor, + indicatorSize: indicatorSize, + indicator: indicator, + indicatorColor: indicatorColor, + labelColor: labelColor, + unselectedLabelColor: unselectedLabelColor, + overlayColor: overlayColor, + tabs: tabs, + padding: padding, + labelPadding: labelPadding, + labelStyle: labelStyle, + unselectedLabelStyle: unselectedLabelStyle, + splashBorderRadius: splashBorderRadius, + indicatorPadding: indicatorPadding, + onTap: onTap); + } - TabBar? tabBar; + if (emptyTabs) { + return tabBar; + } - if (secondary) { - tabBar = TabBar.secondary( - tabAlignment: tabAlignment, - controller: _tabController, - isScrollable: isScrollable, - dividerHeight: dividerHeight, - enableFeedback: enableFeedback, - mouseCursor: mouseCursor, - indicatorWeight: indicatorWeight, - dividerColor: dividerColor, - indicatorSize: indicatorSize, - indicator: indicator, - indicatorColor: indicatorColor, - labelColor: labelColor, - unselectedLabelColor: unselectedLabelColor, - overlayColor: overlayColor, - tabs: tabs, - padding: padding, - labelPadding: labelPadding, - labelStyle: labelStyle, - unselectedLabelStyle: unselectedLabelStyle, - splashBorderRadius: splashBorderRadius, - indicatorPadding: indicatorPadding ?? EdgeInsets.zero, - onTap: onTap); - } else { - tabBar = TabBar( - tabAlignment: tabAlignment, + var child = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + tabBar, + Expanded( + child: TabBarView( controller: _tabController, - isScrollable: isScrollable, - dividerHeight: dividerHeight, - enableFeedback: enableFeedback, - mouseCursor: mouseCursor, - indicatorWeight: indicatorWeight, - dividerColor: dividerColor, - indicatorSize: indicatorSize, - indicator: indicator, - indicatorColor: indicatorColor, - labelColor: labelColor, - unselectedLabelColor: unselectedLabelColor, - overlayColor: overlayColor, - tabs: tabs, - padding: padding, - labelPadding: labelPadding, - labelStyle: labelStyle, - unselectedLabelStyle: unselectedLabelStyle, - splashBorderRadius: splashBorderRadius, - indicatorPadding: indicatorPadding ?? EdgeInsets.zero, - onTap: onTap); - } - - debugPrint("tabs.length: ${tabBar.tabs.length}"); - - if (emptyTabs) { - return tabBar; - } - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - tabBar, - Expanded( - child: TabBarView( - controller: _tabController, - clipBehavior: clipBehavior, - children: viewModel.controlViews.map((tabView) { - var contentCtrls = tabView.children - .where((c) => c.name == "content" && c.isVisible); - if (contentCtrls.isEmpty) { - return const SizedBox.shrink(); - } - return createControl( - widget.control, contentCtrls.first.id, disabled, - parentAdaptive: adaptive); - }).toList())) - ], - ); - }); + clipBehavior: clipBehavior, + children: widget.control.children("tabs").map((tab) { + return tab.buildWidget("content") ?? const SizedBox.shrink(); + }).toList())) + ], + ); - return constrainedControl(context, tabs, widget.parent, widget.control); + return ConstrainedControl(control: widget.control, child: child); } } diff --git a/packages/flet/lib/src/controls/text.dart b/packages/flet/lib/src/controls/text.dart index 6cf810e94..59eb32ba2 100644 --- a/packages/flet/lib/src/controls/text.dart +++ b/packages/flet/lib/src/controls/text.dart @@ -1,191 +1,151 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; +import '../utils/colors.dart'; import '../utils/numbers.dart'; import '../utils/text.dart'; -import 'create_control.dart'; -import 'flet_store_mixin.dart'; +import 'base_controls.dart'; -class TextControl extends StatelessWidget with FletStoreMixin { - final Control? parent; +class TextControl extends StatelessWidget { final Control control; - final bool parentDisabled; - final FletControlBackend backend; - const TextControl( - {super.key, - required this.parent, - required this.control, - required this.parentDisabled, - required this.backend}); + const TextControl({super.key, required this.control}); @override Widget build(BuildContext context) { - var result = withControlTree(control, (context, viewModel) { - debugPrint("Text build: ${control.id}"); - - bool disabled = control.isDisabled || parentDisabled; - - String text = control.attrString("value", "")!; - var selectionCursorColor = - control.attrColor("selectionCursorColor", context); - var selectionCursorWidth = - control.attrDouble("selectionCursorWidth", 2.0)!; - var selectionCursorHeight = control.attrDouble("selectionCursorHeight"); - var showSelectionCursor = control.attrBool("showSelectionCursor", false)!; - var enableInteractiveSelection = - control.attrBool("enableInteractiveSelection", true)!; - - List? spans = parseTextSpans( - Theme.of(context), - viewModel, - disabled, - (String controlId, String eventName, String eventData) { - backend.triggerControlEvent(controlId, eventName, eventData); - }, - ); - String? semanticsLabel = control.attrString("semanticsLabel"); - bool noWrap = control.attrBool("noWrap", false)!; - int? maxLines = control.attrInt("maxLines"); - - TextStyle? style; - var styleNameOrData = control.attrString("style", null); - if (styleNameOrData != null) { - style = getTextStyle(context, styleNameOrData); - } - if (style == null && styleNameOrData != null) { - try { - style = parseTextStyle(Theme.of(context), control, "style"); - } on FormatException catch (_) { - style = null; - } - } - - TextStyle? themeStyle; - var styleName = control.attrString("theme_style", null); - if (styleName != null) { - themeStyle = getTextStyle(context, styleName); - } - - if (style == null && themeStyle != null) { - style = themeStyle; - } else if (style != null && themeStyle != null) { - style = themeStyle.merge(style); - } - - var fontWeight = control.attrString("weight", "")!; - - List variations = []; - if (fontWeight.startsWith("w")) { - variations.add( - FontVariation('wght', parseDouble(fontWeight.substring(1), 0)!)); - } - - style = (style ?? const TextStyle()).copyWith( - fontSize: control.attrDouble("size", null), - fontWeight: getFontWeight(fontWeight), - fontStyle: control.attrBool( - "italic", - false, - )! - ? FontStyle.italic - : null, - fontFamily: control.attrString("fontFamily"), - fontVariations: variations, - color: control.attrColor("color", context) ?? - (spans.isNotEmpty - ? DefaultTextStyle.of(context).style.color - : null), - backgroundColor: control.attrColor("bgcolor", context), - ); - - TextAlign textAlign = - parseTextAlign(control.attrString("textAlign"), TextAlign.start)!; - - TextOverflow overflow = - parseTextOverflow(control.attrString("overflow"), TextOverflow.clip)!; - - onSelectionChanged( - TextSelection selection, SelectionChangedCause? cause) { - debugPrint("Text ${control.id} selection changed"); - backend.triggerControlEvent( - control.id, - "selection_change", - jsonEncode({ - "text": text ?? "", - "start": selection.start, - "end": selection.end, - "base_offset": selection.baseOffset, - "extent_offset": selection.extentOffset, - "affinity": selection.affinity.name, - "directional": selection.isDirectional, - "collapsed": selection.isCollapsed, - "valid": selection.isValid, - "normalized": selection.isNormalized, - "cause": cause?.name ?? "unknown", - })); + debugPrint("Text build: ${control.id}"); + + String text = control.getString("value", "")!; + var selectionCursorColor = + control.getColor("selection_cursor_color", context); + var selectionCursorWidth = + control.getDouble("selection_cursor_width", 2.0)!; + var selectionCursorHeight = control.getDouble("selection_cursor_height"); + var showSelectionCursor = control.getBool("show_selection_cursor", false)!; + var enableInteractiveSelection = + control.getBool("enable_interactive_selection", true)!; + + List? spans = parseTextSpans( + control.children("spans"), + Theme.of(context), + (Control control, String eventName, [dynamic eventData]) { + control.triggerEvent(eventName, eventData); + }, + ); + String? semanticsLabel = control.getString("semantics_label"); + bool noWrap = control.getBool("no_wrap", false)!; + int? maxLines = control.getInt("max_lines"); + + TextStyle? style; + var styleNameOrData = control.getString("style", null); + if (styleNameOrData != null) { + style = getTextStyle(context, styleNameOrData); + } + if (style == null && styleNameOrData != null) { + try { + style = control.getTextStyle("style", Theme.of(context)); + } on FormatException catch (_) { + style = null; } - - onTap() { - debugPrint("Text ${control.id} selection changed"); - backend.triggerControlEvent( - control.id, - "selection_change", - ); - } - - return control.attrBool("selectable", false)! - ? (spans.isNotEmpty) - ? SelectableText.rich( - TextSpan(text: text, style: style, children: spans), - maxLines: maxLines, - textAlign: textAlign, - cursorColor: selectionCursorColor, - cursorHeight: selectionCursorHeight, - cursorWidth: selectionCursorWidth, - semanticsLabel: semanticsLabel, - showCursor: showSelectionCursor, - enableInteractiveSelection: enableInteractiveSelection, - onSelectionChanged: onSelectionChanged, - onTap: onTap, - ) - : SelectableText( - text, - semanticsLabel: semanticsLabel, - maxLines: maxLines, - style: style, - textAlign: textAlign, - cursorColor: selectionCursorColor, - cursorHeight: selectionCursorHeight, - cursorWidth: selectionCursorWidth, - showCursor: showSelectionCursor, - enableInteractiveSelection: enableInteractiveSelection, - onSelectionChanged: onSelectionChanged, - onTap: onTap, - ) - : (spans.isNotEmpty) - ? Text.rich( - TextSpan(text: text, style: style, children: spans), - semanticsLabel: semanticsLabel, - maxLines: maxLines, - softWrap: !noWrap, - textAlign: textAlign, - overflow: overflow, - ) - : Text( - text, - semanticsLabel: semanticsLabel, - maxLines: maxLines, - softWrap: !noWrap, - style: style, - textAlign: textAlign, - overflow: overflow, - ); - }); - - return constrainedControl(context, result, parent, control); + } + + TextStyle? themeStyle; + var styleName = control.getString("theme_style"); + if (styleName != null) { + themeStyle = getTextStyle(context, styleName); + } + + if (style == null && themeStyle != null) { + style = themeStyle; + } else if (style != null && themeStyle != null) { + style = themeStyle.merge(style); + } + + var fontWeight = control.getString("weight", "")!; + + List variations = []; + if (fontWeight.startsWith("w")) { + variations + .add(FontVariation('wght', parseDouble(fontWeight.substring(1), 0)!)); + } + + style = (style ?? const TextStyle()).copyWith( + fontSize: control.getDouble("size", null), + fontWeight: getFontWeight(fontWeight), + fontStyle: control.getBool("italic", false)! ? FontStyle.italic : null, + fontFamily: control.getString("font_family"), + fontVariations: variations, + color: control.getColor("color", context) ?? + (spans.isNotEmpty ? DefaultTextStyle.of(context).style.color : null), + backgroundColor: control.getColor("bgcolor", context), + ); + + TextAlign textAlign = + parseTextAlign(control.getString("text_align"), TextAlign.start)!; + + TextOverflow overflow = + parseTextOverflow(control.getString("overflow"), TextOverflow.clip)!; + + onSelectionChanged(TextSelection selection, SelectionChangedCause? cause) { + control.triggerEvent("selection_change", { + "text": text, + "cause": cause?.name ?? "unknown", + "selection": selection.toMap(), + }); + } + + onTap() { + control.triggerEvent("tap"); + } + + var textWidget = control.getBool("selectable", false)! + ? (spans.isNotEmpty) + ? SelectableText.rich( + TextSpan(text: text, style: style, children: spans), + maxLines: maxLines, + textAlign: textAlign, + cursorColor: selectionCursorColor, + cursorHeight: selectionCursorHeight, + cursorWidth: selectionCursorWidth, + semanticsLabel: semanticsLabel, + showCursor: showSelectionCursor, + enableInteractiveSelection: enableInteractiveSelection, + onSelectionChanged: onSelectionChanged, + onTap: onTap, + ) + : SelectableText( + text, + semanticsLabel: semanticsLabel, + maxLines: maxLines, + style: style, + textAlign: textAlign, + cursorColor: selectionCursorColor, + cursorHeight: selectionCursorHeight, + cursorWidth: selectionCursorWidth, + showCursor: showSelectionCursor, + enableInteractiveSelection: enableInteractiveSelection, + onSelectionChanged: onSelectionChanged, + onTap: onTap, + ) + : (spans.isNotEmpty) + ? Text.rich( + TextSpan(text: text, style: style, children: spans), + semanticsLabel: semanticsLabel, + maxLines: maxLines, + softWrap: !noWrap, + textAlign: textAlign, + overflow: overflow, + ) + : Text( + text, + semanticsLabel: semanticsLabel, + maxLines: maxLines, + softWrap: !noWrap, + style: style, + textAlign: textAlign, + overflow: overflow, + ); + + return ConstrainedControl(control: control, child: textWidget); } } diff --git a/packages/flet/lib/src/controls/text_button.dart b/packages/flet/lib/src/controls/text_button.dart deleted file mode 100644 index 981ae752d..000000000 --- a/packages/flet/lib/src/controls/text_button.dart +++ /dev/null @@ -1,188 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../flet_control_backend.dart'; -import '../models/control.dart'; -import '../utils/buttons.dart'; -import '../utils/icons.dart'; -import '../utils/launch_url.dart'; -import '../utils/others.dart'; -import 'create_control.dart'; -import 'cupertino_button.dart'; -import 'cupertino_dialog_action.dart'; -import 'flet_store_mixin.dart'; - -class TextButtonControl extends StatefulWidget { - final Control? parent; - final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - - const TextButtonControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); - - @override - State createState() => _TextButtonControlState(); -} - -class _TextButtonControlState extends State - with FletStoreMixin { - late final FocusNode _focusNode; - String? _lastFocusValue; - - @override - void initState() { - super.initState(); - _focusNode = FocusNode(); - _focusNode.addListener(_onFocusChange); - } - - @override - void dispose() { - _focusNode.removeListener(_onFocusChange); - _focusNode.dispose(); - super.dispose(); - } - - void _onFocusChange() { - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); - } - - @override - Widget build(BuildContext context) { - debugPrint("Button build: ${widget.control.id}"); - - return withPagePlatform((context, platform) { - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - if (adaptive == true && - (platform == TargetPlatform.iOS || - platform == TargetPlatform.macOS)) { - return widget.control.name == "action" && - (widget.parent?.type == "alertdialog" || - widget.parent?.type == "cupertinoalertdialog") - ? CupertinoDialogActionControl( - control: widget.control, - parentDisabled: widget.parentDisabled, - parentAdaptive: adaptive, - children: widget.children, - backend: widget.backend) - : CupertinoButtonControl( - control: widget.control, - parentDisabled: widget.parentDisabled, - parentAdaptive: adaptive, - children: widget.children, - backend: widget.backend); - } - - String text = widget.control.attrString("text", "")!; - var clipBehavior = parseClip(widget.control.attrString("clipBehavior")); - IconData? icon = parseIcon(widget.control.attrString("icon")); - Color? iconColor = widget.control.attrColor("iconColor", context); - var contentCtrls = - widget.children.where((c) => c.name == "content" && c.isVisible); - bool onHover = widget.control.attrBool("onHover", false)!; - bool onLongPress = widget.control.attrBool("onLongPress", false)!; - String url = widget.control.attrString("url", "")!; - String? urlTarget = widget.control.attrString("urlTarget"); - bool autofocus = widget.control.attrBool("autofocus", false)!; - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - Function()? onPressed = !disabled - ? () { - debugPrint("Button ${widget.control.id} clicked!"); - if (url != "") { - openWebBrowser(url, webWindowName: urlTarget); - } - widget.backend.triggerControlEvent(widget.control.id, "click"); - } - : null; - - Function()? onLongPressHandler = onLongPress && !disabled - ? () { - debugPrint("Button ${widget.control.id} long pressed!"); - widget.backend - .triggerControlEvent(widget.control.id, "long_press"); - } - : null; - - Function(bool)? onHoverHandler = onHover && !disabled - ? (state) { - debugPrint("Button ${widget.control.id} hovered!"); - widget.backend.triggerControlEvent( - widget.control.id, "hover", state.toString()); - } - : null; - - TextButton? button; - - var theme = Theme.of(context); - - var style = parseButtonStyle(Theme.of(context), widget.control, "style", - defaultForegroundColor: theme.colorScheme.primary, - defaultBackgroundColor: Colors.transparent, - defaultOverlayColor: Colors.transparent, - defaultShadowColor: Colors.transparent, - defaultSurfaceTintColor: Colors.transparent, - defaultElevation: 0, - defaultPadding: const EdgeInsets.all(8), - defaultBorderSide: BorderSide.none, - defaultShape: theme.useMaterial3 - ? const StadiumBorder() - : RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))); - - if (icon != null) { - button = TextButton.icon( - autofocus: autofocus, - focusNode: _focusNode, - onPressed: onPressed, - onLongPress: onLongPressHandler, - onHover: onHoverHandler, - style: style, - clipBehavior: clipBehavior, - icon: Icon( - icon, - color: iconColor, - ), - label: Text(text)); - } else if (contentCtrls.isNotEmpty) { - button = TextButton( - autofocus: autofocus, - focusNode: _focusNode, - onPressed: onPressed, - onLongPress: onLongPressHandler, - onHover: onHoverHandler, - clipBehavior: clipBehavior, - style: style, - child: - createControl(widget.control, contentCtrls.first.id, disabled)); - } else { - button = TextButton( - autofocus: autofocus, - focusNode: _focusNode, - style: style, - onPressed: onPressed, - onLongPress: onLongPressHandler, - onHover: onHoverHandler, - clipBehavior: clipBehavior, - child: Text(text)); - } - - var focusValue = widget.control.attrString("focus"); - if (focusValue != null && focusValue != _lastFocusValue) { - _lastFocusValue = focusValue; - _focusNode.requestFocus(); - } - - return constrainedControl(context, button, widget.parent, widget.control); - }); - } -} diff --git a/packages/flet/lib/src/controls/textfield.dart b/packages/flet/lib/src/controls/textfield.dart index 0912377be..562516952 100644 --- a/packages/flet/lib/src/controls/textfield.dart +++ b/packages/flet/lib/src/controls/textfield.dart @@ -1,45 +1,18 @@ -import 'package:flet/src/utils/platform.dart'; +import 'package:flet/flet.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import '../flet_control_backend.dart'; -import '../models/control.dart'; -import '../utils/autofill.dart'; -import '../utils/borders.dart'; -import '../utils/edge_insets.dart'; -import '../utils/form_field.dart'; -import '../utils/mouse.dart'; -import '../utils/others.dart'; -import '../utils/overlay_style.dart'; -import '../utils/text.dart'; -import '../utils/textfield.dart'; -import 'create_control.dart'; -import 'cupertino_textfield.dart'; -import 'flet_store_mixin.dart'; - class TextFieldControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - final FletControlBackend backend; - const TextFieldControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive, - required this.backend}); + TextFieldControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _TextFieldControlState(); } -class _TextFieldControlState extends State - with FletStoreMixin { +class _TextFieldControlState extends State { String _value = ""; bool _revealPassword = false; bool _focused = false; @@ -58,7 +31,7 @@ class _TextFieldControlState extends State if (!HardwareKeyboard.instance.isShiftPressed && evt.logicalKey.keyLabel == 'Enter') { if (evt is KeyDownEvent) { - widget.backend.triggerControlEvent(widget.control.id, "submit"); + widget.control.triggerEvent("submit"); } return KeyEventResult.handled; } else { @@ -69,6 +42,7 @@ class _TextFieldControlState extends State _shiftEnterfocusNode.addListener(_onShiftEnterFocusChange); _focusNode = FocusNode(); _focusNode.addListener(_onFocusChange); + widget.control.addInvokeMethodListener(_invokeMethod); } @override @@ -77,368 +51,225 @@ class _TextFieldControlState extends State _shiftEnterfocusNode.removeListener(_onShiftEnterFocusChange); _shiftEnterfocusNode.dispose(); _focusNode.removeListener(_onFocusChange); + widget.control.removeInvokeMethodListener(_invokeMethod); _focusNode.dispose(); super.dispose(); } + Future _invokeMethod(String name, dynamic args) async { + debugPrint("TextField.$name($args)"); + switch (name) { + case "focus": + _focusNode.requestFocus(); + default: + throw Exception("Unknown TextField method: $name"); + } + } + void _onShiftEnterFocusChange() { - setState(() { - _focused = _shiftEnterfocusNode.hasFocus; - }); - widget.backend.triggerControlEvent( - widget.control.id, _shiftEnterfocusNode.hasFocus ? "focus" : "blur"); + _focused = _shiftEnterfocusNode.hasFocus; + widget.control + .triggerEvent(_shiftEnterfocusNode.hasFocus ? "focus" : "blur"); } void _onFocusChange() { - setState(() { - _focused = _focusNode.hasFocus; - }); - widget.backend.triggerControlEvent( - widget.control.id, _focusNode.hasFocus ? "focus" : "blur"); + _focused = _focusNode.hasFocus; + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); } @override Widget build(BuildContext context) { debugPrint("TextField build: ${widget.control.id}"); - return withPagePlatform((context, platform) { - bool autofocus = widget.control.attrBool("autofocus", false)!; - bool disabled = widget.control.isDisabled || widget.parentDisabled; - - bool? adaptive = - widget.control.attrBool("adaptive") ?? widget.parentAdaptive; - if (adaptive == true && - (platform == TargetPlatform.iOS || - platform == TargetPlatform.macOS)) { - return CupertinoTextFieldControl( - control: widget.control, - children: widget.children, - parent: widget.parent, - parentDisabled: widget.parentDisabled, - parentAdaptive: adaptive, - backend: widget.backend); - } - - debugPrint("TextField build: ${widget.control.id}"); - - String value = widget.control.attrs["value"] ?? ""; - if (_value != value) { - _value = value; - _controller.value = TextEditingValue( - text: value, - selection: TextSelection.collapsed( - offset: value.length), // preserve cursor position at the end - ); - } - - var prefixControls = - widget.children.where((c) => c.name == "prefix" && c.isVisible); - var prefixIconControls = - widget.children.where((c) => c.name == "prefix_icon" && c.isVisible); - var suffixControls = - widget.children.where((c) => c.name == "suffix" && c.isVisible); - var suffixIconControls = - widget.children.where((c) => c.name == "suffix_icon" && c.isVisible); - var iconControls = - widget.children.where((c) => c.name == "icon" && c.isVisible); - var counterControls = - widget.children.where((c) => c.name == "counter" && c.isVisible); - var errorCtrl = - widget.children.where((c) => c.name == "error" && c.isVisible); - var helperCtrl = - widget.children.where((c) => c.name == "helper" && c.isVisible); - var labelCtrl = - widget.children.where((c) => c.name == "label" && c.isVisible); - - bool shiftEnter = widget.control.attrBool("shiftEnter", false)!; - bool multiline = - widget.control.attrBool("multiline", false)! || shiftEnter; - int minLines = widget.control.attrInt("minLines", 1)!; - int? maxLines = widget.control.attrInt("maxLines", multiline ? null : 1); - - bool password = widget.control.attrBool("password", false)!; - bool canRevealPassword = - widget.control.attrBool("canRevealPassword", false)!; - var cursorColor = widget.control.attrColor("cursorColor", context); - var selectionColor = widget.control.attrColor("selectionColor", context); - var textSize = widget.control.attrDouble("textSize"); - var color = widget.control.attrColor("color", context); - var focusedColor = widget.control.attrColor("focusedColor", context); - - TextStyle? textStyle = - parseTextStyle(Theme.of(context), widget.control, "textStyle"); - if (textSize != null || color != null || focusedColor != null) { - textStyle = (textStyle ?? const TextStyle()).copyWith( - fontSize: textSize, - color: _focused ? focusedColor ?? color : color); - } - - TextCapitalization textCapitalization = parseTextCapitalization( - widget.control.attrString("capitalization"), - TextCapitalization.none)!; + bool autofocus = widget.control.getBool("autofocus", false)!; - FilteringTextInputFormatter? inputFilter = - parseInputFilter(widget.control, "inputFilter"); - - List? inputFormatters = []; - // add non-null input formatters - if (inputFilter != null) { - inputFormatters.add(inputFilter); - } - if (textCapitalization != TextCapitalization.none) { - inputFormatters.add(TextCapitalizationFormatter(textCapitalization)); - } - - Widget? revealPasswordIcon; - if (password && canRevealPassword) { - revealPasswordIcon = GestureDetector( - child: Icon( - _revealPassword ? Icons.visibility_off : Icons.visibility, - ), - onTap: () { - setState(() { - _revealPassword = !_revealPassword; - }); - }); - } - - double? textVerticalAlign = - widget.control.attrDouble("textVerticalAlign"); + String value = widget.control.getString("value", "")!; + if (_value != value) { + _value = value; + _controller.value = TextEditingValue( + text: value, + selection: TextSelection.collapsed( + offset: value.length), // preserve cursor position at the end + ); + } - FocusNode focusNode = shiftEnter ? _shiftEnterfocusNode : _focusNode; + var shiftEnter = widget.control.getBool("shift_enter", false)!; + var multiline = widget.control.getBool("multiline", false)! || shiftEnter; + var minLines = widget.control.getInt("min_lines", 1)!; + var maxLines = widget.control.getInt("max_lines", multiline ? null : 1); + + var password = widget.control.getBool("password", false)!; + var canRevealPassword = + widget.control.getBool("can_reveal_password", false)!; + var cursorColor = widget.control.getColor("cursor_color", context); + var selectionColor = widget.control.getColor("selection_color", context); + var textSize = widget.control.getDouble("text_size"); + var color = widget.control.getColor("color", context); + var focusedColor = widget.control.getColor("focused_color", context); + var textStyle = widget.control + .getTextStyle("text_style", Theme.of(context), const TextStyle())!; + if (textSize != null || color != null || focusedColor != null) { + textStyle = textStyle.copyWith( + fontSize: textSize, color: _focused ? focusedColor ?? color : color); + } - var focusValue = widget.control.attrString("focus"); - var blurValue = widget.control.attrString("blur"); - if (focusValue != null && focusValue != _lastFocusValue) { - _lastFocusValue = focusValue; - focusNode.requestFocus(); - } - if (blurValue != null && blurValue != _lastBlurValue) { - _lastBlurValue = blurValue; - _focusNode.unfocus(); - } + TextCapitalization textCapitalization = widget.control + .getTextCapitalization("capitalization", TextCapitalization.none)!; - var fitParentSize = widget.control.attrBool("fitParentSize", false)!; + FilteringTextInputFormatter? inputFilter = + parseInputFilter(widget.control.get("input_filter")); - var maxLength = widget.control.attrInt("maxLength"); + List? inputFormatters = []; + // add non-null input formatters + if (inputFilter != null) { + inputFormatters.add(inputFilter); + } + if (textCapitalization != TextCapitalization.none) { + inputFormatters.add(TextCapitalizationFormatter(textCapitalization)); + } - Widget textField = TextFormField( - style: textStyle, - autofocus: autofocus, - enabled: !disabled, - onFieldSubmitted: !multiline - ? (value) { - widget.backend - .triggerControlEvent(widget.control.id, "submit", value); - } - : null, - decoration: buildInputDecoration(context, widget.control, - prefix: prefixControls.isNotEmpty ? prefixControls.first : null, - prefixIcon: prefixIconControls.isNotEmpty - ? prefixIconControls.first - : null, - suffix: suffixControls.isNotEmpty ? suffixControls.first : null, - suffixIcon: suffixIconControls.isNotEmpty - ? suffixIconControls.first - : null, - icon: iconControls.isNotEmpty ? iconControls.first : null, - counter: - counterControls.isNotEmpty ? counterControls.first : null, - error: errorCtrl.isNotEmpty ? errorCtrl.first : null, - helper: helperCtrl.isNotEmpty ? helperCtrl.first : null, - label: labelCtrl.isNotEmpty ? labelCtrl.first : null, - customSuffix: revealPasswordIcon, - valueLength: _value.length, - maxLength: maxLength, - focused: _focused, - disabled: disabled, - adaptive: adaptive), - showCursor: widget.control.attrBool("showCursor"), - textAlignVertical: textVerticalAlign != null - ? TextAlignVertical(y: textVerticalAlign) - : null, - cursorHeight: widget.control.attrDouble("cursorHeight"), - cursorWidth: widget.control.attrDouble("cursorWidth", 2.0)!, - cursorRadius: parseRadius(widget.control, "cursorRadius"), - keyboardType: multiline - ? TextInputType.multiline - : parseTextInputType(widget.control.attrString("keyboardType"), - TextInputType.text)!, - autocorrect: widget.control.attrBool("autocorrect", true)!, - enableSuggestions: - widget.control.attrBool("enableSuggestions", true)!, - smartDashesType: widget.control.attrBool("smartDashesType", true)! - ? SmartDashesType.enabled - : SmartDashesType.disabled, - smartQuotesType: widget.control.attrBool("smartQuotesType", true)! - ? SmartQuotesType.enabled - : SmartQuotesType.disabled, - textAlign: parseTextAlign( - widget.control.attrString("textAlign"), TextAlign.start)!, - minLines: fitParentSize ? null : minLines, - maxLines: fitParentSize ? null : maxLines, - maxLength: maxLength, - readOnly: widget.control.attrBool("readOnly", false)!, - inputFormatters: inputFormatters.isNotEmpty ? inputFormatters : null, - obscureText: password && !_revealPassword, - controller: _controller, - focusNode: focusNode, - autofillHints: parseAutofillHints(widget.control, "autofillHints"), - expands: fitParentSize, - enableInteractiveSelection: - widget.control.attrBool("enableInteractiveSelection"), - canRequestFocus: widget.control.attrBool("canRequestFocus", true)!, - clipBehavior: parseClip( - widget.control.attrString("clipBehavior"), Clip.hardEdge)!, - cursorColor: cursorColor, - ignorePointers: widget.control.attrBool("ignorePointers"), - cursorErrorColor: - widget.control.attrColor("cursorErrorColor", context), - scribbleEnabled: widget.control.attrBool("enableScribble", true)!, - scrollPadding: parseEdgeInsets( - widget.control, "scrollPadding", const EdgeInsets.all(20.0))!, - keyboardAppearance: - parseBrightness(widget.control.attrString("keyboardBrightness")), - enableIMEPersonalizedLearning: - widget.control.attrBool("enableIMEPersonalizedLearning", true)!, - obscuringCharacter: - widget.control.attrString("obscuringCharacter", '•')!, - mouseCursor: - parseMouseCursor(widget.control.attrString("mouseCursor")), - cursorOpacityAnimates: widget.control.attrBool("animateCursorOpacity", - Theme.of(context).platform == TargetPlatform.iOS)!, - onTapAlwaysCalled: - widget.control.attrBool("animateCursorOpacity", false)!, - strutStyle: parseStrutStyle(widget.control, "strutStyle"), + Widget? revealPasswordIcon; + if (password && canRevealPassword) { + revealPasswordIcon = GestureDetector( + child: Icon( + _revealPassword ? Icons.visibility_off : Icons.visibility, + ), onTap: () { - widget.backend.triggerControlEvent(widget.control.id, "click"); - }, - onTapOutside: widget.control.attrBool("onTapOutside", false)! - ? (PointerDownEvent? event) { - widget.backend - .triggerControlEvent(widget.control.id, "tapOutside"); - } - : null, - onChanged: (String value) { - _value = value; - widget.backend - .updateControlState(widget.control.id, {"value": value}); - if (widget.control.attrBool("onChange", false)!) { - widget.backend - .triggerControlEvent(widget.control.id, "change", value); - } + setState(() { + _revealPassword = !_revealPassword; + }); }); - - if (cursorColor != null || selectionColor != null) { - textField = TextSelectionTheme( - data: TextSelectionTheme.of(context).copyWith( - cursorColor: cursorColor, selectionColor: selectionColor), - child: textField); - } - - // linux workaround for https://github.com/flet-dev/flet/issues/3934 - textField = - isLinuxDesktop() ? ExcludeSemantics(child: textField) : textField; - - if (widget.control.attrInt("expand", 0)! > 0) { - return constrainedControl( - context, textField, widget.parent, widget.control); - } else { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - if (constraints.maxWidth == double.infinity && - widget.control.attrDouble("width") == null) { - textField = ConstrainedBox( - constraints: const BoxConstraints.tightFor(width: 300), - child: textField, - ); - } - - return constrainedControl( - context, textField, widget.parent, widget.control); - }, - ); - } - }); - } -} - -class TextCapitalizationFormatter extends TextInputFormatter { - final TextCapitalization capitalization; - - TextCapitalizationFormatter(this.capitalization); - - @override - TextEditingValue formatEditUpdate( - TextEditingValue oldValue, TextEditingValue newValue) { - String text = ''; - - switch (capitalization) { - case TextCapitalization.words: - text = capitalizeFirstofEach(newValue.text); - break; - case TextCapitalization.sentences: - List sentences = newValue.text.split('.'); - for (int i = 0; i < sentences.length; i++) { - sentences[i] = inCaps(sentences[i]); - } - text = sentences.join('.'); - break; - case TextCapitalization.characters: - text = allInCaps(newValue.text); - break; - case TextCapitalization.none: - text = newValue.text; - break; } - return TextEditingValue( - text: text, - selection: newValue.selection, - ); - } + var textVerticalAlign = widget.control.getDouble("text_vertical_align"); - /// 'Hello world' - static String inCaps(String text) { - if (text.isEmpty) { - return text; + FocusNode focusNode = shiftEnter ? _shiftEnterfocusNode : _focusNode; + var focusValue = widget.control.getString("focus"); + var blurValue = widget.control.getString("blur"); + if (focusValue != null && focusValue != _lastFocusValue) { + _lastFocusValue = focusValue; + focusNode.requestFocus(); } - String result = ''; - for (int i = 0; i < text.length; i++) { - if (text[i] != ' ') { - result += '${text[i].toUpperCase()}${text.substring(i + 1)}'; - break; - } else { - result += text[i]; - } + if (blurValue != null && blurValue != _lastBlurValue) { + _lastBlurValue = blurValue; + _focusNode.unfocus(); } - return result; - } - - /// 'HELLO WORLD' - static String allInCaps(String text) => text.toUpperCase(); - - /// 'Hello World' - static String capitalizeFirstofEach(String text) => text - .replaceAll(RegExp(' +'), ' ') - .split(" ") - .map((str) => inCaps(str)) - .join(" "); -} -class CustomNumberFormatter extends TextInputFormatter { - final String pattern; + var fitParentSize = widget.control.getBool("fit_parent_size", false)!; + var maxLength = widget.control.getInt("max_length"); + + Widget textField = TextFormField( + style: textStyle, + autofocus: autofocus, + enabled: !widget.control.disabled, + onFieldSubmitted: !multiline + ? (value) { + widget.control.triggerEvent("submit", value); + } + : null, + decoration: buildInputDecoration( + context, + widget.control, + customSuffix: revealPasswordIcon, + valueLength: _value.length, + maxLength: maxLength, + focused: _focused, + ), + showCursor: widget.control.getBool("show_cursor"), + textAlignVertical: textVerticalAlign != null + ? TextAlignVertical(y: textVerticalAlign) + : null, + cursorHeight: widget.control.getDouble("cursor_height"), + cursorWidth: widget.control.getDouble("cursor_width", 2.0)!, + cursorRadius: widget.control.getRadius("cursor_radius"), + keyboardType: multiline + ? TextInputType.multiline + : widget.control + .getTextInputType("keyboard_type", TextInputType.text)!, + autocorrect: widget.control.getBool("autocorrect", true)!, + enableSuggestions: widget.control.getBool("enable_suggestions", true)!, + smartDashesType: widget.control.getBool("smart_dashes_type", true)! + ? SmartDashesType.enabled + : SmartDashesType.disabled, + smartQuotesType: widget.control.getBool("smart_quotes_type", true)! + ? SmartQuotesType.enabled + : SmartQuotesType.disabled, + textAlign: widget.control.getTextAlign("text_align", TextAlign.start)!, + minLines: fitParentSize ? null : minLines, + maxLines: fitParentSize ? null : maxLines, + maxLength: maxLength, + readOnly: widget.control.getBool("read_only", false)!, + inputFormatters: inputFormatters.isNotEmpty ? inputFormatters : null, + obscureText: password && !_revealPassword, + controller: _controller, + focusNode: focusNode, + autofillHints: widget.control.getAutofillHints("autofill_hints"), + expands: fitParentSize, + enableInteractiveSelection: + widget.control.getBool("enable_interactive_selection"), + canRequestFocus: widget.control.getBool("can_request_focus", true)!, + clipBehavior: + widget.control.getClipBehavior("clip_behavior", Clip.hardEdge)!, + cursorColor: cursorColor, + ignorePointers: widget.control.getBool("ignore_pointers"), + cursorErrorColor: + widget.control.getColor("cursor_error_color", context), + stylusHandwritingEnabled: + widget.control.getBool("enable_stylus_handwriting", true)!, + scrollPadding: widget.control + .getPadding("scroll_padding", const EdgeInsets.all(20.0))!, + keyboardAppearance: widget.control.getBrightness("keyboard_brightness"), + enableIMEPersonalizedLearning: + widget.control.getBool("enable_ime_personalized_learning", true)!, + obscuringCharacter: + widget.control.getString("obscuring_character", '•')!, + mouseCursor: widget.control.getMouseCursor("mouse_cursor"), + cursorOpacityAnimates: widget.control.getBool("animate_cursor_opacity", + Theme.of(context).platform == TargetPlatform.iOS)!, + onTapAlwaysCalled: widget.control.getBool("always_call_on_tap", false)!, + strutStyle: widget.control.getStrutStyle("strut_style"), + onTap: () { + widget.control.triggerEvent("click"); + }, + onTapOutside: widget.control.getBool("on_tap_outside", false)! + ? (PointerDownEvent? event) { + widget.control.triggerEvent("tap_outside"); + } + : null, + onChanged: (String value) { + _value = value; + widget.control.updateProperties({"value": value}); + if (widget.control.getBool("on_change", false)!) { + widget.control.triggerEvent("change", value); + } + }); - CustomNumberFormatter(this.pattern); + if (cursorColor != null || selectionColor != null) { + textField = TextSelectionTheme( + data: TextSelectionTheme.of(context).copyWith( + cursorColor: cursorColor, selectionColor: selectionColor), + child: textField); + } - @override - TextEditingValue formatEditUpdate( - TextEditingValue oldValue, TextEditingValue newValue) { - final regExp = RegExp(pattern); - if (regExp.hasMatch(newValue.text)) { - return newValue; + // linux workaround for https://github.com/flet-dev/flet/issues/3934 + textField = + isLinuxDesktop() ? ExcludeSemantics(child: textField) : textField; + + if (widget.control.get("expand") == true || + (widget.control.get("expand") is int && + widget.control.getInt("expand", 0)! > 0)) { + return ConstrainedControl(control: widget.control, child: textField); + } else { + double? width = widget.control.getDouble("width"); + + return ConstrainedControl( + control: widget.control, + child: width == null + ? ConstrainedBox( + constraints: const BoxConstraints.tightFor(width: 300), + child: textField) + : textField, + ); } - // If newValue is invalid, keep the old value - return oldValue; } } diff --git a/packages/flet/lib/src/controls/time_picker.dart b/packages/flet/lib/src/controls/time_picker.dart index f584e0045..ada1d8c25 100644 --- a/packages/flet/lib/src/controls/time_picker.dart +++ b/packages/flet/lib/src/controls/time_picker.dart @@ -1,23 +1,16 @@ import 'package:flutter/material.dart'; -import '../flet_control_backend.dart'; import '../models/control.dart'; -import '../utils/others.dart'; +import '../utils/colors.dart'; +import '../utils/misc.dart'; +import '../utils/numbers.dart'; +import '../utils/time.dart'; class TimePickerControl extends StatefulWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final FletControlBackend backend; - const TimePickerControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.backend}); + TimePickerControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); @override State createState() => _TimePickerControlState(); @@ -28,48 +21,34 @@ class _TimePickerControlState extends State { Widget build(BuildContext context) { debugPrint("TimePicker build: ${widget.control.id}"); - bool lastOpen = widget.control.state["open"] ?? false; + bool lastOpen = widget.control.getBool("_open", false)!; - var open = widget.control.attrBool("open", false)!; - TimeOfDay value = widget.control.attrTime("value", TimeOfDay.now())!; + var open = widget.control.getBool("open", false)!; + var value = widget.control.getTimeOfDay("value", TimeOfDay.now())!; void onClosed(TimeOfDay? timeValue) { - String stringValue; - String eventName; - if (timeValue == null) { - String hourString = value.hour.toString(); - String minuteString = value.minute.toString(); - stringValue = '$hourString:$minuteString'; - eventName = "dismiss"; - } else { - String hourString = timeValue.hour.toString(); - String minuteString = timeValue.minute.toString(); - stringValue = '$hourString:$minuteString'; - eventName = "change"; + widget.control.updateProperties({"_open": false}, python: false); + widget.control.updateProperties({"value": timeValue, "open": false}); + if (timeValue != null) { + widget.control.triggerEvent("change", timeValue); } - widget.control.state["open"] = false; - widget.backend.updateControlState( - widget.control.id, {"value": stringValue, "open": "false"}); - widget.backend - .triggerControlEvent(widget.control.id, eventName, stringValue); + widget.control.triggerEvent("dismiss", timeValue == null); } Widget createSelectTimeDialog() { Widget dialog = TimePickerDialog( initialTime: value, - helpText: widget.control.attrString("helpText"), - cancelText: widget.control.attrString("cancelText"), - confirmText: widget.control.attrString("confirmText"), - hourLabelText: widget.control.attrString("hourLabelText"), - minuteLabelText: widget.control.attrString("minuteLabelText"), - errorInvalidText: widget.control.attrString("errorInvalidText"), - initialEntryMode: parseTimePickerEntryMode( - widget.control.attrString("timePickerEntryMode"), - TimePickerEntryMode.dial)!, - orientation: parseOrientation(widget.control.attrString("orientation")), + helpText: widget.control.getString("help_text"), + cancelText: widget.control.getString("cancel_text"), + confirmText: widget.control.getString("confirm_text"), + hourLabelText: widget.control.getString("hour_label_text"), + minuteLabelText: widget.control.getString("minute_label_text"), + errorInvalidText: widget.control.getString("error_invalid_text"), + initialEntryMode: widget.control.getTimePickerEntryMode( + "time_picker_entry_mode", TimePickerEntryMode.dial)!, + orientation: widget.control.getOrientation("orientation"), onEntryModeChanged: (TimePickerEntryMode mode) { - widget.backend.triggerControlEvent( - widget.control.id, "entryModeChange", mode.name); + widget.control.triggerEvent("entry_mode_change", mode.name); }, ); @@ -77,15 +56,15 @@ class _TimePickerControlState extends State { } if (open && (open != lastOpen)) { - widget.control.state["open"] = open; + widget.control.updateProperties({"_open": open}, python: false); WidgetsBinding.instance.addPostFrameCallback((_) { showDialog( - barrierColor: widget.control.attrColor("barrierColor", context), + barrierColor: widget.control.getColor("barrier_color", context), + barrierDismissible: !widget.control.getBool("modal", false)!, useRootNavigator: false, context: context, builder: (context) => createSelectTimeDialog()).then((result) { - debugPrint("pickTime() completed"); onClosed(result); }); }); diff --git a/packages/flet/lib/src/controls/transparent_pointer.dart b/packages/flet/lib/src/controls/transparent_pointer.dart index 20f60273f..1d4c363e0 100644 --- a/packages/flet/lib/src/controls/transparent_pointer.dart +++ b/packages/flet/lib/src/controls/transparent_pointer.dart @@ -1,42 +1,23 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import '../extensions/control.dart'; import '../models/control.dart'; -import 'create_control.dart'; +import 'base_controls.dart'; class TransparentPointerControl extends StatelessWidget { - final Control? parent; final Control control; - final List children; - final bool parentDisabled; - final bool? parentAdaptive; - - const TransparentPointerControl( - {super.key, - this.parent, - required this.control, - required this.children, - required this.parentDisabled, - required this.parentAdaptive}); + + const TransparentPointerControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("TransparentPointer build: ${control.id}"); - var contentCtrls = - children.where((c) => c.name == "content" && c.isVisible); - bool disabled = control.isDisabled || parentDisabled; - - return constrainedControl( - context, - TransparentPointer( - transparent: true, - child: contentCtrls.isNotEmpty - ? createControl(control, contentCtrls.first.id, disabled, - parentAdaptive: parentAdaptive) - : null), - parent, - control); + var pointer = TransparentPointer( + transparent: true, child: control.buildWidget("content")); + + return ConstrainedControl(control: control, child: pointer); } } diff --git a/packages/flet/lib/src/controls/vertical_divider.dart b/packages/flet/lib/src/controls/vertical_divider.dart index 5576f0889..bd6b975e5 100644 --- a/packages/flet/lib/src/controls/vertical_divider.dart +++ b/packages/flet/lib/src/controls/vertical_divider.dart @@ -1,26 +1,27 @@ import 'package:flutter/material.dart'; import '../models/control.dart'; -import 'create_control.dart'; +import '../utils/colors.dart'; +import '../utils/numbers.dart'; +import 'base_controls.dart'; class VerticalDividerControl extends StatelessWidget { - final Control? parent; final Control control; - const VerticalDividerControl( - {super.key, required this.parent, required this.control}); + const VerticalDividerControl({super.key, required this.control}); @override Widget build(BuildContext context) { debugPrint("VerticalDivider build: ${control.id}"); + var divider = VerticalDivider( - width: control.attrDouble("width"), - thickness: control.attrDouble("thickness"), - color: control.attrColor("color", context), - indent: control.attrDouble("leadingIndent"), - endIndent: control.attrDouble("trailingIndent"), + width: control.getDouble("width"), + thickness: control.getDouble("thickness"), + color: control.getColor("color", context), + indent: control.getDouble("leading_indent"), + endIndent: control.getDouble("trailing_indent"), ); - return baseControl(context, divider, parent, control); + return BaseControl(control: control, child: divider); } } diff --git a/packages/flet/lib/src/controls/view.dart b/packages/flet/lib/src/controls/view.dart new file mode 100644 index 000000000..68e6cb77d --- /dev/null +++ b/packages/flet/lib/src/controls/view.dart @@ -0,0 +1,347 @@ +import 'dart:async'; + +import 'package:collection/collection.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; + +import '../controls/control_widget.dart'; +import '../extensions/control.dart'; +import '../flet_backend.dart'; +import '../models/control.dart'; +import '../models/page_design.dart'; +import '../utils/alignment.dart'; +import '../utils/box.dart'; +import '../utils/buttons.dart'; +import '../utils/colors.dart'; +import '../utils/edge_insets.dart'; +import '../utils/numbers.dart'; +import '../utils/platform.dart'; +import '../utils/theme.dart'; +import '../widgets/loading_page.dart'; +import '../widgets/page_context.dart'; +import '../widgets/page_media.dart'; +import '../widgets/scaffold_key_provider.dart'; +import 'app_bar.dart'; +import 'cupertino_app_bar.dart'; +import 'scroll_notification_control.dart'; +import 'scrollable_control.dart'; + +class ViewControl extends StatefulWidget { + final Control control; + + ViewControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); + + @override + State createState() => _ViewControlState(); +} + +class _ViewControlState extends State { + final _scaffoldKey = GlobalKey(); + Control? _overlay; + Control? _dialogs; + Completer? _popCompleter; + + @override + void initState() { + debugPrint("View.initState: ${widget.control.id}"); + super.initState(); + _overlay = widget.control.parent?.child("_overlay"); + _overlay?.addListener(_overlayOrDialogsChanged); + _dialogs = widget.control.parent?.child("_dialogs"); + _dialogs?.addListener(_overlayOrDialogsChanged); + widget.control.addInvokeMethodListener(_invokeMethod); + } + + @override + void didUpdateWidget(covariant ViewControl oldWidget) { + debugPrint("View.didUpdateWidget: ${widget.control.id}"); + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + debugPrint("View.dispose: ${widget.control.id}"); + _overlay?.removeListener(_overlayOrDialogsChanged); + widget.control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } + + Future _invokeMethod(String name, dynamic args) async { + debugPrint("View.$name($args)"); + switch (name) { + case "confirm_pop": + if (_popCompleter != null && !_popCompleter!.isCompleted) { + _popCompleter?.complete(args["should_pop"]); + } + default: + throw Exception("Unknown View method: $name"); + } + } + + void _overlayOrDialogsChanged() { + setState(() {}); + } + + Future _dismissDrawer(Control drawer, FletBackend backend) async { + await Future.delayed(const Duration(milliseconds: 250)); + backend.updateControl(drawer.id, {"open": false}); + backend.triggerControlEvent(drawer, "dismiss"); + } + + void _openDrawers(Control? drawer, Control? endDrawer) { + if (drawer != null && + drawer.getBool("open", false) == true && + _scaffoldKey.currentState?.isDrawerOpen == false) { + _scaffoldKey.currentState?.openDrawer(); + } else if (endDrawer != null && + endDrawer.getBool("open", false) == true && + _scaffoldKey.currentState?.isEndDrawerOpen == false) { + _scaffoldKey.currentState?.openEndDrawer(); + } + } + + @override + Widget build(BuildContext context) { + debugPrint("View.build: ${widget.control.id}"); + + var control = widget.control; + + final mainAlignment = parseMainAxisAlignment( + control.getString("vertical_alignment"), MainAxisAlignment.start)!; + final crossAlignment = parseCrossAxisAlignment( + control.getString("horizontal_alignment"), CrossAxisAlignment.start)!; + + final textDirection = control.parent!.getBool("rtl", false)! + ? TextDirection.rtl + : TextDirection.ltr; + + var column = Column( + mainAxisAlignment: mainAlignment, + crossAxisAlignment: crossAlignment, + spacing: control.getDouble("spacing", 10)!, + children: control + .children("controls") + .where((c) => c.visible) + .map((child) => + ControlWidget(control: child, key: ValueKey(child.id))) + .toList()); + + Widget child = ScrollableControl( + control: control, scrollDirection: Axis.vertical, child: column); + + if (control.getBool("on_scroll", false)!) { + child = ScrollNotificationControl(control: control, child: child); + } + + var pageData = PageContext.of(context); + + Control? appBar = control.child("appbar"); + Widget? appBarWidget; + if (appBar != null) { + appBar.notifyParent = true; + appBarWidget = pageData?.widgetsDesign == PageDesign.cupertino || + appBar.type == "CupertinoAppBar" + ? CupertinoAppBarControl(control: appBar) + as ObstructingPreferredSizeWidget + : AppBarControl(control: appBar); + } + + List overlayWidgets = []; + var pageViews = control.parent!.children("views"); + var overlayControls = _overlay?.children("controls"); + var dialogControls = _dialogs?.children("controls"); + + Control? drawer = dialogControls?.firstWhereOrNull( + (c) => c.type == "NavigationDrawer" && c.get("position") != "end"); + Control? endDrawer = dialogControls?.firstWhereOrNull( + (c) => c.type == "NavigationDrawer" && c.get("position") == "end"); + + var isRootView = control.id == pageViews.first.id; + + if (overlayControls != null && dialogControls != null) { + if (control.id == pageViews.last.id) { + overlayWidgets + .addAll(overlayControls.map((c) => ControlWidget(control: c))); + overlayWidgets.addAll(dialogControls + .where((dialog) => dialog.type != "NavigationDrawer") + .map((c) => ControlWidget(control: c))); + overlayWidgets.add(const PageMedia()); + } + + var windowControl = control.parent?.get("window"); + if (windowControl != null && isRootView && isDesktopPlatform()) { + overlayWidgets.add(ControlWidget(control: windowControl)); + } + } + + WidgetsBinding.instance.addPostFrameCallback((_) { + _openDrawers(drawer, endDrawer); + }); + + Widget body = Stack(children: [ + SizedBox.expand( + child: Container( + padding: parsePadding( + control.get("padding"), const EdgeInsets.all(10))!, + child: child)), + ...overlayWidgets + ]); + + var materialTheme = pageData?.themeMode == ThemeMode.light || + ((pageData?.themeMode == null || + pageData?.themeMode == ThemeMode.system) && + pageData?.brightness == Brightness.light) + ? parseTheme(control.parent!.get("theme"), context, Brightness.light) + : control.parent!.getString("dark_theme") != null + ? parseTheme( + control.parent!.get("dark_theme"), context, Brightness.dark) + : parseTheme( + control.parent!.get("theme"), context, Brightness.dark); + + Widget scaffold = ScaffoldKeyProvider( + scaffoldKey: _scaffoldKey, + child: Scaffold( + key: appBarWidget == null || appBarWidget is AppBarControl + ? _scaffoldKey + : null, + backgroundColor: control.getColor("bgcolor", context) ?? + ((pageData?.widgetsDesign == PageDesign.cupertino) + ? CupertinoTheme.of(context).scaffoldBackgroundColor + : Theme.of(context).scaffoldBackgroundColor), + appBar: appBarWidget is AppBarControl ? appBarWidget : null, + drawer: drawer != null ? ControlWidget(control: drawer) : null, + onDrawerChanged: (opened) { + if (!opened) { + _dismissDrawer(drawer!, FletBackend.of(context)); + } + }, + endDrawer: endDrawer != null ? ControlWidget(control: endDrawer) : null, + onEndDrawerChanged: (opened) { + if (!opened) { + _dismissDrawer(endDrawer!, FletBackend.of(context)); + } + }, + body: body, + bottomNavigationBar: control.buildWidget("navigation_bar") ?? + control.buildWidget("bottom_appbar"), + floatingActionButton: control.buildWidget("floating_action_button"), + floatingActionButtonLocation: control.getFloatingActionButtonLocation( + "floating_action_button_location", + FloatingActionButtonLocation.endFloat), + ), + ); + + var systemOverlayStyle = + materialTheme.extension(); + + if (systemOverlayStyle != null && + systemOverlayStyle.systemUiOverlayStyle != null && + appBarWidget == null) { + scaffold = AnnotatedRegion( + value: systemOverlayStyle.systemUiOverlayStyle!, + child: scaffold, + ); + } + + if (appBarWidget is CupertinoAppBarControl) { + scaffold = CupertinoPageScaffold( + key: _scaffoldKey, + backgroundColor: control.getColor("bgcolor", context), + navigationBar: appBarWidget, + child: scaffold); + } + + if (pageData?.widgetsDesign == PageDesign.material) { + scaffold = CupertinoTheme( + data: pageData?.themeMode == ThemeMode.light || + ((pageData?.themeMode == null || + pageData?.themeMode == ThemeMode.system) && + pageData?.brightness == Brightness.light) + ? parseCupertinoTheme( + control.parent!.get("theme"), context, Brightness.light) + : control.parent!.getString("dark_theme") != null + ? parseCupertinoTheme( + control.parent!.get("dark_theme"), context, Brightness.dark) + : parseCupertinoTheme( + control.parent!.get("theme"), context, Brightness.dark), + child: scaffold, + ); + } else if (pageData?.widgetsDesign == PageDesign.cupertino) { + scaffold = Theme( + data: materialTheme, + child: scaffold, + ); + } + + var showAppStartupScreen = + FletBackend.of(context).showAppStartupScreen ?? false; + var appStartupScreenMessage = + FletBackend.of(context).appStartupScreenMessage ?? ""; + + var appStatus = + context.select( + (backend) => (isLoading: backend.isLoading, error: backend.error)); + + Widget? loadingPage; + if ((appStatus.isLoading || appStatus.error != "") && + showAppStartupScreen) { + loadingPage = LoadingPage( + isLoading: appStatus.isLoading, + message: + appStatus.isLoading ? appStartupScreenMessage : appStatus.error, + ); + } + + Widget result = Directionality( + textDirection: textDirection, + child: loadingPage != null + ? Stack( + children: [scaffold, loadingPage], + ) + : scaffold); + + var backgroundDecoration = control.getBoxDecoration("decoration", context); + var foregroundDecoration = + control.getBoxDecoration("foreground_decoration", context); + if (backgroundDecoration != null || foregroundDecoration != null) { + result = Container( + decoration: backgroundDecoration, + foregroundDecoration: foregroundDecoration, + child: result, + ); + } + + result = PopScope( + canPop: control.getBool("can_pop", true)!, + onPopInvokedWithResult: (didPop, result) { + if (didPop || !control.getBool("on_confirm_pop", false)!) { + return; + } + debugPrint("Page.onPopInvokedWithResult()"); + if (_popCompleter != null && !_popCompleter!.isCompleted) { + _popCompleter!.completeError("Aborted"); + } + _popCompleter = Completer(); + control.triggerEvent("confirm_pop"); + _popCompleter!.future + .timeout( + const Duration(minutes: 5), + onTimeout: () => false, + ) + .then((shouldPop) { + if (context.mounted && shouldPop) { + if (isRootView) { + SystemNavigator.pop(); + } else { + Navigator.pop(context); + } + } + }).onError((e, st) {/* do nothing */}); + }, + child: result); + return result; + } +} diff --git a/packages/flet/lib/src/controls/window.dart b/packages/flet/lib/src/controls/window.dart new file mode 100644 index 000000000..fe79d22cf --- /dev/null +++ b/packages/flet/lib/src/controls/window.dart @@ -0,0 +1,436 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:window_manager/window_manager.dart'; + +import '../flet_backend.dart'; +import '../models/control.dart'; +import '../utils/alignment.dart'; +import '../utils/colors.dart'; +import '../utils/desktop.dart'; +import '../utils/numbers.dart'; +import '../utils/theme.dart'; +import '../utils/window.dart'; + +class WindowControl extends StatefulWidget { + final Control control; + + WindowControl({Key? key, required this.control}) + : super(key: ValueKey("control_${control.id}")); + + @override + State createState() => _WindowControlState(); +} + +class _WindowControlState extends State with WindowListener { + String? _title; + Color? _bgColor; + double? _width; + double? _height; + double? _minWidth; + double? _minHeight; + double? _maxWidth; + double? _maxHeight; + double? _top; + double? _left; + double? _opacity; + double? _aspectRatio; + Brightness? _brightness; + bool? _minimizable; + bool? _maximizable; + bool? _fullScreen; + bool? _movable; + bool? _resizable; + bool? _alwaysOnTop; + bool? _alwaysOnBottom; + bool? _preventClose; + bool? _minimized; + bool? _maximized; + Alignment? _alignment; + String? _badgeLabel; + String? _icon; + bool? _hasShadow; + bool? _visible; + bool? _focused; + bool? _frameless; + bool? _titleBarHidden; + bool? _skipTaskBar; + double? _progressBar; + bool? _ignoreMouseEvents; + final Completer _initWindowStateCompleter = Completer(); + + @override + void initState() { + debugPrint("Window.initState()"); + super.initState(); + _initWindowState(); + } + + Future _initWindowState() async { + final windowState = await getWindowState(); + _width = windowState.width; + _height = windowState.height; + _top = windowState.top; + _left = windowState.left; + _opacity = windowState.opacity; + _minimizable = windowState.minimizable; + _maximizable = windowState.maximizable; + _fullScreen = windowState.fullScreen; + _resizable = windowState.resizable; + _alwaysOnTop = windowState.alwaysOnTop; + _preventClose = windowState.preventClose; + _minimized = windowState.minimized; + _maximized = windowState.maximized; + _visible = windowState.visible; + _focused = windowState.focused; + _skipTaskBar = windowState.skipTaskBar; + + // bind listeners + windowManager.addListener(this); + widget.control.addInvokeMethodListener(_invokeMethod); + + if (!_initWindowStateCompleter.isCompleted) { + _initWindowStateCompleter.complete(); + } + } + + @override + void didChangeDependencies() { + debugPrint("Window.didChangeDependencies: ${widget.control.id}"); + super.didChangeDependencies(); + _updateWindowAfterInit(); + } + + @override + void dispose() { + debugPrint("Window.dispose()"); + windowManager.removeListener(this); + widget.control.addInvokeMethodListener(_invokeMethod); + super.dispose(); + } + + @override + void didUpdateWidget(covariant WindowControl oldWidget) { + debugPrint("Window.didUpdateWidget: ${widget.control.id}"); + super.didUpdateWidget(oldWidget); + _updateWindowAfterInit(); + } + + void _updateWindowAfterInit() { + var backend = FletBackend.of(context); + if (_initWindowStateCompleter.isCompleted) { + _updateWindow(backend); + } else { + _initWindowStateCompleter.future.then((_) { + _updateWindow(backend); + }); + } + } + + void _updateWindow(FletBackend backend) async { + try { + var title = widget.control.parent!.getString("title"); + var bgColor = widget.control.getColor("bgcolor", context); + var width = widget.control.getDouble("width"); + var height = widget.control.getDouble("height"); + var minWidth = widget.control.getDouble("min_width"); + var minHeight = widget.control.getDouble("min_height"); + var maxWidth = widget.control.getDouble("max_width"); + var maxHeight = widget.control.getDouble("max_height"); + var top = widget.control.getDouble("top"); + var left = widget.control.getDouble("left"); + var center = widget.control.getString("center"); + var fullScreen = widget.control.getBool("full_screen"); + var minimized = widget.control.getBool("minimized"); + var maximized = widget.control.getBool("maximized"); + var alignment = widget.control.getAlignment("alignment"); + var badgeLabel = widget.control.getString("badge_label"); + var icon = widget.control.getString("icon"); + var hasShadow = widget.control.getBool("shadow"); + var opacity = widget.control.getDouble("opacity"); + var aspectRatio = widget.control.getDouble("aspect_ratio"); + var brightness = widget.control.getBrightness("brightness"); + var minimizable = widget.control.getBool("minimizable"); + var maximizable = widget.control.getBool("maximizable"); + var alwaysOnTop = widget.control.getBool("always_on_top"); + var alwaysOnBottom = widget.control.getBool("always_on_bottom"); + var resizable = widget.control.getBool("resizable"); + var movable = widget.control.getBool("movable"); + var preventClose = widget.control.getBool("prevent_close"); + var titleBarHidden = widget.control.getBool("title_bar_hidden"); + var titleBarButtonsHidden = + widget.control.getBool("title_bar_buttons_hidden", false)!; + var visible = widget.control.getBool("visible"); + var focused = widget.control.getBool("focused"); + var skipTaskBar = widget.control.getBool("skip_task_bar"); + var frameless = widget.control.getBool("frameless"); + var progressBar = widget.control.getDouble("progress_bar"); + var ignoreMouseEvents = widget.control.getBool("ignore_mouse_events"); + + // title + if (title != null && title != _title) { + setWindowTitle(title); + _title = title; + } + + // bgColor + if (bgColor != null && bgColor != _bgColor) { + setWindowBackgroundColor(bgColor); + _bgColor = bgColor; + } + + // size + if ((width != null || height != null) && + (width != _width || height != _height) && + fullScreen != true && + (defaultTargetPlatform != TargetPlatform.macOS || + (defaultTargetPlatform == TargetPlatform.macOS && + widget.control.getBool("maximized") != true && + widget.control.getBool("minimized") != true))) { + await setWindowSize(width, height); + _width = width; + _height = height; + } + + // min size + if ((minWidth != null || minHeight != null) && + (minWidth != _minWidth || minHeight != _minHeight)) { + await setWindowMinSize(minWidth, minHeight); + _minWidth = minWidth; + _minHeight = minHeight; + } + + // max size + if ((maxWidth != null || maxHeight != null) && + (maxWidth != _maxWidth || maxHeight != _maxHeight)) { + await setWindowMaxSize(maxWidth, maxHeight); + _maxWidth = maxWidth; + _maxHeight = maxHeight; + } + + // position + if ((top != null || left != null) && + (top != _top || left != _left) && + fullScreen != true && + (center == null || center == "") && + (defaultTargetPlatform != TargetPlatform.macOS || + (defaultTargetPlatform == TargetPlatform.macOS && + widget.control.getBool("maximized") != true && + widget.control.getBool("minimized") != true))) { + await setWindowPosition(top, left); + _top = top; + _left = left; + } + + // opacity + if (opacity != null && opacity != _opacity) { + await setWindowOpacity(opacity); + _opacity = opacity; + } + + // aspectRatio + if (aspectRatio != null && aspectRatio != _aspectRatio) { + await setWindowAspectRatio(aspectRatio); + _aspectRatio = aspectRatio; + } + + // brightness + if (brightness != null && brightness != _brightness) { + await setWindowBrightness(brightness); + _brightness = brightness; + } + + // minimizable + if (minimizable != null && minimizable != _minimizable) { + await setWindowMinimizability(minimizable); + _minimizable = minimizable; + } + + // minimized + if (minimized != _minimized) { + if (minimized == true) { + await minimizeWindow(); + } else if (minimized == false && maximized != true) { + await restoreWindow(); + } + _minimized = minimized; + } + + // maximizable + if (maximizable != null && maximizable != _maximizable) { + await setWindowMaximizability(maximizable); + _maximizable = maximizable; + } + + // maximized + if (maximized != _maximized) { + if (maximized == true) { + await maximizeWindow(); + } else if (maximized == false) { + await unmaximizeWindow(); + } + _maximized = maximized; + } + + // alignment + if (alignment != null && alignment != _alignment) { + await setWindowAlignment(alignment); + _alignment = alignment; + } + + // badge label + if (badgeLabel != null && badgeLabel != _badgeLabel) { + await setWindowBadgeLabel(badgeLabel); + _badgeLabel = badgeLabel; + } + + // icon + if (icon != null && icon != _icon) { + var iconAssetSrc = backend.getAssetSource(icon); + await setWindowIcon(iconAssetSrc.path); + _icon = icon; + } + + // has shadow + if (hasShadow != null && hasShadow != _hasShadow) { + await setWindowShadow(hasShadow); + _hasShadow = hasShadow; + } + + // resizable + if (resizable != null && resizable != _resizable) { + await setWindowResizability(resizable); + _resizable = resizable; + } + + // movable + if (movable != null && movable != _movable) { + await setWindowMovability(movable); + _movable = movable; + } + + // full screen + if (fullScreen != null && fullScreen != _fullScreen) { + await setWindowFullScreen(fullScreen); + _fullScreen = fullScreen; + } + + // always on top + if (alwaysOnTop != null && alwaysOnTop != _alwaysOnTop) { + await setWindowAlwaysOnTop(alwaysOnTop); + _alwaysOnTop = alwaysOnTop; + } + + // always on bottom + if (alwaysOnBottom != null && alwaysOnBottom != _alwaysOnBottom) { + await setWindowAlwaysOnBottom(alwaysOnBottom); + _alwaysOnBottom = alwaysOnBottom; + } + + // prevent close + if (preventClose != null && preventClose != _preventClose) { + await setWindowPreventClose(preventClose); + _preventClose = preventClose; + } + + // title bar hidden + if (titleBarHidden != null && titleBarHidden != _titleBarHidden) { + await setWindowTitleBarVisibility( + titleBarHidden, titleBarButtonsHidden); + _titleBarHidden = titleBarHidden; + } + + // visible + if (visible != _visible) { + if (visible == true) { + await showWindow(); + } else { + await hideWindow(); + } + _visible = visible; + } + + // focused + if (focused != _focused) { + if (focused == true) { + await focusWindow(); + } else { + await blurWindow(); + } + _focused = focused; + } + + // frameless + if (frameless != null && frameless != _frameless && frameless == true) { + await setWindowFrameless(); + _frameless = frameless; + } + + // progress bar + if (progressBar != null && progressBar != _progressBar) { + await setWindowProgressBar(progressBar); + _progressBar = progressBar; + } + + // skip task bar + if (skipTaskBar != null && skipTaskBar != _skipTaskBar) { + await setWindowSkipTaskBar(skipTaskBar); + _skipTaskBar = skipTaskBar; + } + + // ignore mouse events + if (ignoreMouseEvents != null && + ignoreMouseEvents != _ignoreMouseEvents) { + await setIgnoreMouseEvents(ignoreMouseEvents); + _ignoreMouseEvents = ignoreMouseEvents; + } + } catch (e) { + debugPrint("Error updating window: $e"); + } + } + + Future _invokeMethod(String name, dynamic args) async { + switch (name) { + case "wait_until_ready_to_show": + await waitUntilReadyToShow(); + break; + case "to_front": + windowToFront(); + break; + case "center": + await centerWindow(); + break; + case "close": + await closeWindow(); + break; + case "destroy": + await destroyWindow(); + break; + case "start_dragging": + await startDraggingWindow(); + break; + case "start_resizing": + var edge = parseWindowResizeEdge(args["edge"]); + if (edge != null) { + await startResizingWindow(edge); + } + break; + default: + throw Exception("Unknown method ${widget.control.type}.$name"); + } + } + + @override + Widget build(BuildContext context) { + return const SizedBox.shrink(); + } + + @override + void onWindowEvent(String eventName) { + if (["resize", "resized", "move"].contains(eventName)) return; + getWindowState().then((wmd) { + widget.control.backend.onWindowEvent(eventName, wmd); + }); + } +} diff --git a/packages/flet/lib/src/controls/window_drag_area.dart b/packages/flet/lib/src/controls/window_drag_area.dart new file mode 100644 index 000000000..69bb1d2a5 --- /dev/null +++ b/packages/flet/lib/src/controls/window_drag_area.dart @@ -0,0 +1,61 @@ +import 'package:flet/src/extensions/control.dart'; +import 'package:flet/src/utils/events.dart'; +import 'package:flet/src/utils/numbers.dart'; +import 'package:flutter/material.dart'; +import 'package:window_manager/window_manager.dart'; + +import '../models/control.dart'; +import '../widgets/error.dart'; +import 'base_controls.dart'; + +class WindowDragAreaControl extends StatelessWidget { + final Control control; + + const WindowDragAreaControl({super.key, required this.control}); + + @override + Widget build(BuildContext context) { + debugPrint("WindowDragArea build: ${control.id}"); + + var content = control.buildWidget("content"); + + if (content == null) { + return const ErrorControl( + "WindowDragArea.content must be provided and visible"); + } + + final wda = GestureDetector( + behavior: HitTestBehavior.translucent, + onPanStart: (DragStartDetails details) { + windowManager.startDragging(); + if (control.getBool("on_drag_start", false)!) { + control.triggerEvent("drag_start", details.toMap()); + } + }, + onPanEnd: (DragEndDetails details) { + if (control.getBool("on_drag_end", false)!) { + control.triggerEvent("drag_end", details.toMap()); + } + }, + onDoubleTap: control.getBool("maximizable", true)! + ? () async { + final isMaximized = await windowManager.isMaximized(); + if (isMaximized) { + windowManager.unmaximize(); + } else { + windowManager.maximize(); + } + + // trigger event + if (control.getBool("on_double_tap", false)!) { + control.triggerEvent( + "double_tap", isMaximized ? "unmaximize" : "maximize"); + } + } + : null, + child: content, + ); + + return ConstrainedControl(control: control, child: wda); + } +} diff --git a/packages/flet/lib/src/extensions/control.dart b/packages/flet/lib/src/extensions/control.dart new file mode 100644 index 000000000..64d0a2765 --- /dev/null +++ b/packages/flet/lib/src/extensions/control.dart @@ -0,0 +1,95 @@ +import 'package:flet/src/utils/icons.dart'; +import 'package:flet/src/widgets/error.dart'; +import 'package:flutter/material.dart'; + +import '../controls/control_widget.dart'; +import '../models/control.dart'; + +/// Extension on [Control] to easily convert child or children controls +/// into corresponding [Widget]s using [ControlWidget]. +extension WidgetFromControl on Control { + /// Returns a list of [Widget]s built from the children of this control + /// under the given [propertyName]. + /// + /// If [visibleOnly] is `true` (default), only includes children that are visible. + /// + /// If [notifyParent] is `true`, sets `notifyParent` on each child control. + List buildWidgets(String propertyName, + {bool visibleOnly = true, bool notifyParent = false}) { + return children(propertyName, visibleOnly: visibleOnly).map((child) { + child.notifyParent = notifyParent; + return ControlWidget(control: child); + }).toList(); + } + + /// Returns a single [Widget] built from the child of this control + /// under the given [propertyName], or `null` if not present or not visible. + /// + /// If [visibleOnly] is `true` (default), returns `null` for an invisible child. + /// + /// If [notifyParent] is `true`, sets `notifyParent` on the child control. + /// + /// If [key] is provided, applies it to the returned [ControlWidget]. + Widget? buildWidget(String propertyName, + {bool visibleOnly = true, bool notifyParent = false, Key? key}) { + final c = child(propertyName, visibleOnly: visibleOnly); + if (c == null) return null; + c.notifyParent = notifyParent; + return ControlWidget(key: key, control: c); + } + + Widget? buildIconOrWidget(String propertyName, + {bool visibleOnly = true, + bool notifyParent = false, + Key? key, + Color? color}) { + var icon = get(propertyName); + if (icon is Control) { + Control? c; + c = child(propertyName, visibleOnly: visibleOnly); + if (c == null) return null; + c.notifyParent = notifyParent; + return ControlWidget(key: key, control: c); + } else if (icon is String) { + return Icon(getIcon(propertyName), color: color); + } + return null; + } + + Widget? buildTextOrWidget( + String propertyName, { + Key? key, + bool visibleOnly = true, + bool notifyParent = false, + TextStyle? textStyle, + bool required = false, + Widget? errorWidget, + }) { + var content = get(propertyName); + + if (content is Control) { + return buildWidget(propertyName, + visibleOnly: visibleOnly, notifyParent: notifyParent, key: key); + } + + if (content is String) { + return Text(content, style: textStyle); + } + + if (required) { + return errorWidget ?? + ErrorControl("Error displaying $type", + description: "$propertyName must be specified"); + } + + return null; + } +} + +extension InternalConfiguration on Control { + /// The internal configuration of this control. + /// Represented on Python side by `BaseControl._internals` property. + Map? get internals { + return get("_internals") as Map?; + } +} diff --git a/packages/flet/lib/src/flet_app.dart b/packages/flet/lib/src/flet_app.dart index e9ecc6c0a..5df4192c0 100644 --- a/packages/flet/lib/src/flet_app.dart +++ b/packages/flet/lib/src/flet_app.dart @@ -1,21 +1,27 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; -import 'control_factory.dart'; +import 'controls/control_widget.dart'; import 'flet_app_errors_handler.dart'; -import 'flet_app_main.dart'; -import 'flet_app_services.dart'; +import 'flet_backend.dart'; +import 'flet_extension.dart'; +import 'models/control.dart'; +/// FletApp - The top-level widget that initializes everything class FletApp extends StatefulWidget { final String pageUrl; final String assetsDir; final bool? showAppStartupScreen; final String? appStartupScreenMessage; - final String? controlId; + final int? controlId; final String? title; final FletAppErrorsHandler? errorsHandler; final int? reconnectIntervalMs; final int? reconnectTimeoutMs; - final List? createControlFactories; + final List? extensions; + final Map? args; + final bool? forcePyodide; + final bool multiView; const FletApp( {super.key, @@ -28,39 +34,48 @@ class FletApp extends StatefulWidget { this.errorsHandler, this.reconnectIntervalMs, this.reconnectTimeoutMs, - this.createControlFactories}); + this.extensions, + this.args, + this.forcePyodide, + this.multiView = false}); @override State createState() => _FletAppState(); } class _FletAppState extends State { - String? _pageUrl; - FletAppServices? _appServices; + FletBackend? backend; @override void deactivate() { - _appServices?.close(); + backend?.dispose(); super.deactivate(); } @override Widget build(BuildContext context) { - if (widget.pageUrl != _pageUrl) { - _pageUrl = widget.pageUrl; - _appServices = FletAppServices( - parentAppServices: FletAppServices.maybeOf(context), - showAppStartupScreen: widget.showAppStartupScreen, - appStartupScreenMessage: widget.appStartupScreenMessage, - controlId: widget.controlId, - reconnectIntervalMs: widget.reconnectIntervalMs, - reconnectTimeoutMs: widget.reconnectTimeoutMs, - pageUrl: widget.pageUrl, - assetsDir: widget.assetsDir, - errorsHandler: widget.errorsHandler, - createControlFactories: widget.createControlFactories ?? [], - child: FletAppMain(title: widget.title ?? "Flet")); - } - return _appServices!; + return ChangeNotifierProvider( + create: (context) { + return FletBackend( + showAppStartupScreen: widget.showAppStartupScreen, + appStartupScreenMessage: widget.appStartupScreenMessage, + controlId: widget.controlId, + reconnectIntervalMs: widget.reconnectIntervalMs, + reconnectTimeoutMs: widget.reconnectTimeoutMs, + pageUri: Uri.parse(widget.pageUrl), + assetsDir: widget.assetsDir, + errorsHandler: widget.errorsHandler, + extensions: widget.extensions ?? [], + args: widget.args, + forcePyodide: widget.forcePyodide, + multiView: widget.multiView, + parentFletBackend: + Provider.of(context, listen: false)); + }, + child: Selector( + selector: (_, backend) => backend.page, + builder: (_, page, __) => ControlWidget(control: page), + ), + ); } } diff --git a/packages/flet/lib/src/flet_app_context.dart b/packages/flet/lib/src/flet_app_context.dart deleted file mode 100644 index 8a6e5e821..000000000 --- a/packages/flet/lib/src/flet_app_context.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/material.dart'; - -class FletAppContext extends InheritedWidget { - final ThemeMode? themeMode; - - const FletAppContext( - {super.key, required this.themeMode, required super.child}); - - @override - bool updateShouldNotify(covariant InheritedWidget oldWidget) { - return false; - } - - static FletAppContext? of(BuildContext context) => - context.dependOnInheritedWidgetOfExactType(); -} diff --git a/packages/flet/lib/src/flet_app_main.dart b/packages/flet/lib/src/flet_app_main.dart deleted file mode 100644 index d2cbe32a9..000000000 --- a/packages/flet/lib/src/flet_app_main.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_redux/flutter_redux.dart'; - -import 'controls/create_control.dart'; -import 'flet_app_services.dart'; -import 'models/app_state.dart'; - -class FletAppMain extends StatelessWidget { - final String title; - - const FletAppMain({ - super.key, - required this.title, - }); - - @override - Widget build(BuildContext context) { - return StoreProvider( - store: FletAppServices.of(context).store, - child: createControl(null, "page", false), - ); - } -} diff --git a/packages/flet/lib/src/flet_app_services.dart b/packages/flet/lib/src/flet_app_services.dart deleted file mode 100644 index d65c95bda..000000000 --- a/packages/flet/lib/src/flet_app_services.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:redux/redux.dart'; - -import 'actions.dart'; -import 'control_factory.dart'; -import 'flet_app_errors_handler.dart'; -import 'flet_server.dart'; -import 'flet_server_protocol.dart'; -import 'models/app_state.dart'; -import 'reducers.dart'; - -class FletAppServices extends InheritedWidget { - final FletAppServices? parentAppServices; - final bool? showAppStartupScreen; - final String? appStartupScreenMessage; - final String? controlId; - final int? reconnectIntervalMs; - final int? reconnectTimeoutMs; - final String pageUrl; - final String assetsDir; - final FletAppErrorsHandler? errorsHandler; - late final FletServer server; - late final Store store; - final Map globalKeys = {}; - final Map controlInvokeMethods = {}; - final List createControlFactories; - - FletAppServices( - {super.key, - required super.child, - required this.pageUrl, - required this.assetsDir, - this.errorsHandler, - this.parentAppServices, - this.showAppStartupScreen, - this.appStartupScreenMessage, - this.controlId, - this.reconnectIntervalMs, - this.reconnectTimeoutMs, - required this.createControlFactories}) { - store = Store(appReducer, initialState: AppState.initial()); - server = FletServer(store, controlInvokeMethods, - reconnectIntervalMs: reconnectIntervalMs, - reconnectTimeoutMs: reconnectTimeoutMs, - errorsHandler: errorsHandler); - if (errorsHandler != null) { - if (controlId == null) { - // root error handler - errorsHandler!.addListener(() { - if (store.state.isRegistered) { - server.triggerControlEvent("page", "error", errorsHandler!.error!); - } - }); - } else if (controlId != null && parentAppServices != null) { - // parent error handler - errorsHandler?.addListener(() { - parentAppServices?.server - .triggerControlEvent(controlId!, "error", errorsHandler!.error!); - }); - } - } - // connect to a page - var pageUri = Uri.parse(pageUrl); - store.dispatch(PageLoadAction(pageUri, assetsDir, server)); - } - - @override - bool updateShouldNotify(covariant InheritedWidget oldWidget) { - return false; - } - - void close() { - server.disconnect(); - } - - static FletAppServices? maybeOf(BuildContext context) => - context.dependOnInheritedWidgetOfExactType(); - - static FletAppServices of(BuildContext context) => maybeOf(context)!; -} diff --git a/packages/flet/lib/src/flet_backend.dart b/packages/flet/lib/src/flet_backend.dart new file mode 100644 index 000000000..bf4264bab --- /dev/null +++ b/packages/flet/lib/src/flet_backend.dart @@ -0,0 +1,496 @@ +import 'dart:async'; + +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import 'flet_app_errors_handler.dart'; +import 'flet_core_extension.dart'; +import 'flet_extension.dart'; +import 'models/asset_source.dart'; +import 'models/control.dart'; +import 'models/window_state.dart'; +import 'protocol/control_event_body.dart'; +import 'protocol/invoke_method_request_body.dart'; +import 'protocol/invoke_method_response_body.dart'; +import 'protocol/message.dart'; +import 'protocol/page_media_data.dart'; +import 'protocol/patch_control_request_body.dart'; +import 'protocol/register_client_request_body.dart'; +import 'protocol/register_client_response_body.dart'; +import 'protocol/session_crashed_body.dart'; +import 'protocol/update_control_body.dart'; +import 'transport/flet_backend_channel.dart'; +import 'utils/desktop.dart'; +import 'utils/images.dart'; +import 'utils/numbers.dart'; +import 'utils/platform.dart'; +import 'utils/platform_utils_web.dart' + if (dart.library.io) "utils/platform_utils_non_web.dart"; +import 'utils/session_store_web.dart' + if (dart.library.io) "utils/session_store_non_web.dart"; +import 'utils/uri.dart'; +import 'utils/weak_value_map.dart'; + +/// FletBackend - Handles business logic, provides data, and acts as ChangeNotifier +class FletBackend extends ChangeNotifier { + bool multiView = false; + bool _disposed = false; + final WeakReference? _parentFletBackend; + final Uri pageUri; + final String assetsDir; + final bool? showAppStartupScreen; + final String? appStartupScreenMessage; + final int? controlId; + final FletAppErrorsHandler? errorsHandler; + late final List extensions; + final Map? args; + final bool? forcePyodide; + final Map globalKeys = {}; + + final WeakValueMap controlsIndex = WeakValueMap(); + final int? _reconnectIntervalMs; + final int? _reconnectTimeoutMs; + int _reconnectStarted = 0; + int _reconnectDelayMs = 0; + FletBackendChannel? _backendChannel; + final List _sendQueue = []; + String route = ""; + bool isLoading = true; + final Completer pageSizeUpdated = Completer(); + String error = ""; + Size pageSize = Size.zero; + Map sizeBreakpoints = const { + "xs": 0, + "sm": 576, + "md": 768, + "lg": 992, + "xl": 1200, + "xxl": 1400 + }; + Brightness platformBrightness = Brightness.light; + PageMediaData media = PageMediaData( + padding: PaddingData(EdgeInsets.zero), + viewPadding: PaddingData(EdgeInsets.zero), + viewInsets: PaddingData(EdgeInsets.zero)); + TargetPlatform platform = defaultTargetPlatform; + + late Control _page; + + FletBackend( + {required this.pageUri, + required this.assetsDir, + required this.multiView, + int? reconnectIntervalMs, + int? reconnectTimeoutMs, + this.errorsHandler, + this.showAppStartupScreen, + this.appStartupScreenMessage, + this.controlId, + this.args, + this.forcePyodide, + required extensions, + FletBackend? parentFletBackend}) + : _parentFletBackend = + parentFletBackend != null ? WeakReference(parentFletBackend) : null, + _reconnectTimeoutMs = reconnectTimeoutMs, + _reconnectIntervalMs = reconnectIntervalMs { + // add Flet extension with core controls and services + this.extensions = [...extensions, FletCoreExtension()]; + + // initial "empty" page + _page = Control.fromMap({ + "_c": "Page", + "_i": 1, + "pwa": isProgressiveWebApp(), + "web": kIsWeb, + "debug": kDebugMode, + "wasm": const bool.fromEnvironment('dart.tool.dart2wasm'), + "multi_view": multiView, + "window": { + "_c": "Window", + "_i": 2, + } + }, this); + + _page.addListener(_onPageUpdated); + _onPageUpdated(); + + if (errorsHandler != null) { + if (controlId == null) { + // root error handler + errorsHandler!.addListener(() { + triggerControlEvent(page, "error", errorsHandler!.error!); + }); + } else if (controlId != null && _parentFletBackend != null) { + // parent error handler + errorsHandler?.addListener(() { + _parentFletBackend?.target?.triggerControlEventById( + controlId!, "error", errorsHandler!.error!); + }); + } + } + } + + static FletBackend of(BuildContext context) { + return Provider.of(context, listen: false); + } + + Control get page => _page; + + void _onPageUpdated() { + var newPlatform = parseTargetPlatform( + _page.getString("platform"), defaultTargetPlatform)!; + debugPrint("Page updated: $newPlatform $platform"); + if (newPlatform != platform) { + platform = newPlatform; + notifyListeners(); + } + } + + @override + void dispose() { + debugPrint("Disposing Flet backend."); + _disposed = true; + _page.removeListener(_onPageUpdated); + _page.dispose(); + _backendChannel?.disconnect(); + super.dispose(); + } + + Future connect() async { + debugPrint("Connecting to Flet backend $pageUri..."); + try { + _backendChannel = FletBackendChannel( + address: pageUri.toString(), + args: args ?? {}, + forcePyodide: forcePyodide == true, + onDisconnect: _onDisconnect, + onMessage: _onMessage); + await _backendChannel!.connect(); + _registerClient(); + } catch (e) { + debugPrint("Error connecting to Flet backend: $e"); + error = e.toString(); + _onDisconnect(); + } + } + + _registerClient() { + _send( + Message( + action: MessageAction.registerClient, + payload: RegisterClientRequestBody( + sessionId: SessionStore.getSessionId(pageUri.toString()), + pageName: getWebPageName(pageUri), + page: { + 'route': page.get("route"), + 'pwa': page.get("pwa"), + 'web': page.get("web"), + 'debug': page.get("debug"), + 'wasm': page.get("wasm"), + 'multi_view': page.get("multi_view"), + 'platform_brightness': page.get("platform_brightness"), + 'width': page.get("width"), + 'height': page.get("height"), + 'platform': page.get("platform"), + 'window': page.child("window")!.toMap(), + 'media': page.get("media"), + }).toMap()), + unbuffered: true); + } + + _onClientRegistered(RegisterClientResponseBody resp) { + if (resp.error?.isEmpty ?? true) { + // all good! + // store session ID in a cookie + SessionStore.setSessionId(pageUri.toString(), resp.sessionId); + isLoading = false; + _reconnectDelayMs = 0; + error = ""; + + page.update(resp.patch, shouldNotify: true); + + // drain send queue + debugPrint("Send queue: ${_sendQueue.length}"); + for (var message in _sendQueue) { + _send(message); + } + _sendQueue.clear(); + } else { + // error response! + isLoading = true; + error = resp.error!; + _reconnectDelayMs = 0; + } + _reconnectDelayMs = 0; + notifyListeners(); + } + + void onRouteUpdated(String newRoute) { + debugPrint("Route changed: $newRoute"); + + if (route == "" && isLoading) { + () async { + await pageSizeUpdated.future; + debugPrint("Registering web client with route: $newRoute"); + String platform = defaultTargetPlatform.name.toLowerCase(); + if (platform == "android") { + try { + DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + if (androidInfo.systemFeatures + .contains('android.software.leanback')) { + platform = "android_tv"; + } + } on Exception catch (e) { + debugPrint(e.toString()); + } + } + + // update page details + page.update({"route": newRoute, "platform": platform}, + shouldNotify: false); + + // connect to the server + connect(); + }(); + } else { + // existing route change + debugPrint("New page route: $newRoute"); + _sendRouteChangeEvent(newRoute); + } + + route = newRoute; + notifyListeners(); + } + + /// Triggers a control event for the specified [control]. + /// + /// This method checks if the control has an event handler for the given + /// [eventName] and triggers the event if the application is not in a loading state. + /// + /// - [control]: The control for which the event is triggered. + /// - [eventName]: The name of the event to trigger. + /// - [eventData]: Optional data to pass along with the event. + void triggerControlEvent(Control control, String eventName, + [dynamic eventData]) { + if (control.get("on_$eventName") == true) { + debugPrint("${control.type}(${control.id}).on_$eventName($eventData)"); + triggerControlEventById(control.id, eventName, eventData); + } + } + + void triggerControlEventById(int controlId, String eventName, + [dynamic eventData]) { + _send(Message( + action: MessageAction.controlEvent, + payload: ControlEventBody( + target: controlId, name: eventName, data: eventData) + .toMap())); + } + + void _sendRouteChangeEvent(String route) { + updateControl(page.id, {"route": route}, notify: true); + triggerControlEventById(page.id, "route_change", {"route": route}); + } + + void onWindowEvent(String eventName, WindowState windowState) { + debugPrint("Window event - $eventName: $windowState"); + var window = page.get("window"); + if (window != null && window is Control) { + updateControl(window.id, windowState.toMap()); + triggerControlEvent(window, "event", {"type": eventName}); + notifyListeners(); + } + } + + void updatePageSize(Size newSize) async { + debugPrint("Page size updated: $newSize"); + pageSize = newSize; + var newProps = {"width": newSize.width, "height": newSize.height}; + updateControl(page.id, newProps); + triggerControlEvent(page, "resized", newProps); + + if (isDesktopPlatform()) { + var windowState = await getWindowState(); + debugPrint("Window state updated: $windowState"); + var window = page.child("window")!; + updateControl(window.id, windowState.toMap()); + triggerControlEvent(window, "event", {"type": "resized"}); + } + + if (!pageSizeUpdated.isCompleted) { + pageSizeUpdated.complete(); + } + notifyListeners(); + } + + void updateBrightness(Brightness newBrightness) { + debugPrint("Platform brightness updated: $newBrightness"); + platformBrightness = newBrightness; + updateControl(page.id, {"platform_brightness": newBrightness.name}); + triggerControlEvent(page, "platform_brightness_change"); + notifyListeners(); + } + + void updateMedia(PageMediaData newMedia) { + debugPrint("Page media updated: $newMedia"); + media = newMedia; + updateControl(page.id, {"media": newMedia.toMap()}); + triggerControlEvent(page, "media_change"); + notifyListeners(); + } + + /// Updates the properties of a control with the given [id]. + /// + /// This method takes a control's unique identifier [id] and a map of + /// properties [props] to update the control's state. The [props] map + /// contains key-value pairs where the key is the property name and the + /// value is the new value for that property. + /// + /// - [id]: The unique identifier of the control to be updated. + /// - [props]: A map of property names and their corresponding new values. + /// - [dart]: A boolean indicating whether to apply the patch in Dart. Defaults to `true`. + /// - [python]: A boolean indicating whether to send the update to the Python backend. Defaults to `true`. + /// - [notify]: A boolean indicating whether to notify listeners after applying the patch. Defaults to `false`. + /// + /// This method is typically used to modify the state of a control dynamically. + void updateControl(int id, Map props, + {bool dart = true, bool python = true, bool notify = false}) { + var control = controlsIndex.get(id); + if (control != null) { + if (dart) { + control.update(props, shouldNotify: notify); + } + if (python) { + _send(Message( + action: MessageAction.updateControl, + payload: UpdateControlBody(id: id, props: props).toMap())); + } + } + } + + /// Retrieves the asset source for a given [src]. + /// + /// This method determines the source of an asset based on the provided [src], + /// the current page URI, and the assets directory. It returns an [AssetSource] + /// object that represents the resolved asset source. + /// + /// - [src]: The relative or absolute path to the asset. + /// - Returns: An [AssetSource] object representing the resolved asset source. + AssetSource getAssetSource(String src) { + return getAssetSrc(src, pageUri, assetsDir); + } + + _onMessage(Message message) { + debugPrint("Received message: ${message.toList()}"); + //debugPrint("message.payload: ${message.payload}"); + switch (message.action) { + case MessageAction.registerClient: + _onClientRegistered( + RegisterClientResponseBody.fromJson(message.payload)); + break; + case MessageAction.sessionCrashed: + _onSessionCrashed(SessionCrashedBody.fromJson(message.payload)); + break; + case MessageAction.patchControl: + _onPatchControl(PatchControlRequestBody.fromJson(message.payload)); + break; + case MessageAction.invokeControlMethod: + _onInvokeMethod(InvokeMethodRequestBody.fromJson(message.payload)); + break; + default: + } + } + + _onPatchControl(PatchControlRequestBody req) { + var control = controlsIndex.get(req.id); + if (control != null) { + control.applyPatch(req.patch, this); + //debugPrint("patched control: $control"); + //debugPrint("_controlsIndex.length: ${_controlsIndex.length}"); + } + } + + _onInvokeMethod(InvokeMethodRequestBody req) async { + var control = controlsIndex.get(req.controlId); + dynamic result; + String? error; + if (control != null) { + try { + result = await control.invokeMethod(req.name, req.args, req.timeout); + } catch (e) { + error = e.toString(); + } + } else { + error = + "Calling ${req.name} method of inexistent control: ${req.controlId}"; + } + + _send(Message( + action: MessageAction.invokeControlMethod, + payload: InvokeMethodResponseBody( + controlId: req.controlId, + callId: req.callId, + result: result, + error: error) + .toMap())); + } + + _onSessionCrashed(SessionCrashedBody body) { + error = body.message; + notifyListeners(); + } + + _reconnect(String message, int reconnectDelayMs) { + isLoading = true; + error = message; + _reconnectDelayMs = reconnectDelayMs; + notifyListeners(); + } + + _onDisconnect() { + if (_disposed) { + return; + } + + var nextReconnectDelayMs = _reconnectDelayMs; + if (nextReconnectDelayMs == 0) { + _reconnectStarted = DateTime.now().millisecondsSinceEpoch; + } + + // set/update timeout + nextReconnectDelayMs = nextReconnectDelayMs == 0 || + _backendChannel!.isLocalConnection + ? _reconnectIntervalMs ?? _backendChannel!.defaultReconnectIntervalMs + : nextReconnectDelayMs * 2; + + if (_reconnectTimeoutMs == null || + (DateTime.now().millisecondsSinceEpoch - _reconnectStarted) < + _reconnectTimeoutMs!) { + // re-connect + _reconnect(isUdsPath(pageUri) ? "" : "Loading...", nextReconnectDelayMs); + + debugPrint("Reconnect in $nextReconnectDelayMs milliseconds"); + Future.delayed(Duration(milliseconds: nextReconnectDelayMs)) + .then((value) async { + await connect(); + }); + } else { + errorsHandler?.onError(error != "" + ? error + : "Error connecting to a Flet service in a timely manner."); + } + } + + _send(Message message, {bool unbuffered = false}) { + if (unbuffered || !isLoading) { + debugPrint("_send: ${message.action} ${message.payload}"); + _backendChannel?.send(message); + } else { + _sendQueue.add(message); + } + } +} diff --git a/packages/flet/lib/src/flet_control_backend.dart b/packages/flet/lib/src/flet_control_backend.dart deleted file mode 100644 index 93b3be4b1..000000000 --- a/packages/flet/lib/src/flet_control_backend.dart +++ /dev/null @@ -1,12 +0,0 @@ -abstract class FletControlBackend { - void updateControlState(String controlId, Map props, - {bool client = true, bool server = true}); - - void triggerControlEvent(String controlId, String eventName, - [String? eventData]); - - void subscribeMethods(String controlId, - Future Function(String, Map) methodHandler); - - void unsubscribeMethods(String controlId); -} diff --git a/packages/flet/lib/src/flet_core_extension.dart b/packages/flet/lib/src/flet_core_extension.dart new file mode 100644 index 000000000..649b33975 --- /dev/null +++ b/packages/flet/lib/src/flet_core_extension.dart @@ -0,0 +1,360 @@ +import 'package:flutter/widgets.dart'; + +import 'controls/adaptive_alert_dialog.dart'; +import 'controls/adaptive_button.dart'; +import 'controls/adaptive_checkbox.dart'; +import 'controls/adaptive_radio.dart'; +import 'controls/adaptive_slider.dart'; +import 'controls/adaptive_switch.dart'; +import 'controls/adaptive_texfield.dart'; +import 'controls/animated_switcher.dart'; +import 'controls/app_bar.dart'; +import 'controls/auto_complete.dart'; +import 'controls/autofill_group.dart'; +import 'controls/banner.dart'; +import 'controls/bottom_app_bar.dart'; +import 'controls/bottom_sheet.dart'; +import 'controls/canvas.dart'; +import 'controls/card.dart'; +import 'controls/chip.dart'; +import 'controls/circle_avatar.dart'; +import 'controls/column.dart'; +import 'controls/container.dart'; +import 'controls/control_builder.dart'; +import 'controls/cupertino_action_sheet.dart'; +import 'controls/cupertino_action_sheet_action.dart'; +import 'controls/cupertino_activity_indicator.dart'; +import 'controls/cupertino_alert_dialog.dart'; +import 'controls/cupertino_app_bar.dart'; +import 'controls/cupertino_bottom_sheet.dart'; +import 'controls/cupertino_button.dart'; +import 'controls/cupertino_checkbox.dart'; +import 'controls/cupertino_context_menu.dart'; +import 'controls/cupertino_context_menu_action.dart'; +import 'controls/cupertino_date_picker.dart'; +import 'controls/cupertino_dialog_action.dart'; +import 'controls/cupertino_list_tile.dart'; +import 'controls/cupertino_navigation_bar.dart'; +import 'controls/cupertino_picker.dart'; +import 'controls/cupertino_radio.dart'; +import 'controls/cupertino_segmented_button.dart'; +import 'controls/cupertino_slider.dart'; +import 'controls/cupertino_sliding_segmented_button.dart'; +import 'controls/cupertino_switch.dart'; +import 'controls/cupertino_textfield.dart'; +import 'controls/cupertino_timer_picker.dart'; +import 'controls/datatable.dart'; +import 'controls/date_picker.dart'; +import 'controls/dismissible.dart'; +import 'controls/divider.dart'; +import 'controls/drag_target.dart'; +import 'controls/draggable.dart'; +import 'controls/dropdown.dart'; +import 'controls/dropdownm2.dart'; +import 'controls/expansion_panel.dart'; +import 'controls/expansion_tile.dart'; +import 'controls/flet_app_control.dart'; +import 'controls/floating_action_button.dart'; +import 'controls/gesture_detector.dart'; +import 'controls/grid_view.dart'; +import 'controls/icon.dart'; +import 'controls/icon_button.dart'; +import 'controls/image.dart'; +import 'controls/interactive_viewer.dart'; +import 'controls/list_tile.dart'; +import 'controls/list_view.dart'; +import 'controls/markdown.dart'; +import 'controls/menu_bar.dart'; +import 'controls/menu_item_button.dart'; +import 'controls/merge_semantics.dart'; +import 'controls/navigation_bar.dart'; +import 'controls/navigation_bar_destination.dart'; +import 'controls/navigation_drawer.dart'; +import 'controls/navigation_rail.dart'; +import 'controls/page.dart'; +import 'controls/pagelet.dart'; +import 'controls/placeholder.dart'; +import 'controls/popup_menu_button.dart'; +import 'controls/progress_bar.dart'; +import 'controls/progress_ring.dart'; +import 'controls/radio_group.dart'; +import 'controls/range_slider.dart'; +import 'controls/reorderable_draggable.dart'; +import 'controls/reorderable_list_view.dart'; +import 'controls/responsive_row.dart'; +import 'controls/row.dart'; +import 'controls/safe_area.dart'; +import 'controls/search_bar.dart'; +import 'controls/segmented_button.dart'; +import 'controls/selection_area.dart'; +import 'controls/semantics.dart'; +import 'controls/shader_mask.dart'; +import 'controls/snack_bar.dart'; +import 'controls/stack.dart'; +import 'controls/submenu_button.dart'; +import 'controls/tabs.dart'; +import 'controls/text.dart'; +import 'controls/time_picker.dart'; +import 'controls/transparent_pointer.dart'; +import 'controls/vertical_divider.dart'; +import 'controls/view.dart'; +import 'controls/window.dart'; +import 'controls/window_drag_area.dart'; +import 'flet_extension.dart'; +import 'flet_service.dart'; +import 'models/control.dart'; +import 'services/browser_context_menu.dart'; +import 'services/clipboard.dart'; +import 'services/file_picker.dart'; +import 'services/haptic_feedback.dart'; +import 'services/semantics_service.dart'; +import 'services/shake_detector.dart'; +import 'services/shared_preferences.dart'; +import 'services/storage_paths.dart'; +import 'services/url_launcher.dart'; + +class FletCoreExtension extends FletExtension { + @override + Widget? createWidget(Key? key, Control control) { + switch (control.type) { + case "AlertDialog": + return AdaptiveAlertDialogControl(key: key, control: control); + case "AnimatedSwitcher": + return AnimatedSwitcherControl(key: key, control: control); + case "AppBar": + return AppBarControl(key: key, control: control); + case "AutofillGroup": + return AutofillGroupControl(key: key, control: control); + case "Checkbox": + return AdaptiveCheckboxControl(key: key, control: control); + case "CupertinoCheckbox": + return CupertinoCheckboxControl(key: key, control: control); + case "Banner": + return BannerControl(key: key, control: control); + case "Draggable": + return DraggableControl(key: key, control: control); + case "DragTarget": + return DragTargetControl(key: key, control: control); + case "BottomAppBar": + return BottomAppBarControl(key: key, control: control); + case "BottomSheet": + return BottomSheetControl(key: key, control: control); + case "SearchBar": + return SearchBarControl(key: key, control: control); + case "Card": + return CardControl(key: key, control: control); + case "CupertinoSegmentedButton": + return CupertinoSegmentedButtonControl(key: key, control: control); + case "Markdown": + return MarkdownControl(key: key, control: control); + case "CupertinoDatePicker": + return CupertinoDatePickerControl(key: key, control: control); + case "CupertinoBottomSheet": + return CupertinoBottomSheetControl(key: key, control: control); + case "PopupMenuButton": + return PopupMenuButtonControl(key: key, control: control); + case "ControlBuilder": + return ControlBuilderControl(key: key, control: control); + case "CupertinoSlidingSegmentedButton": + return CupertinoSlidingSegmentedButtonControl( + key: key, control: control); + case "SegmentedButton": + return SegmentedButtonControl(key: key, control: control); + case "Canvas": + return CanvasControl(key: key, control: control); + case "Semantics": + return SemanticsControl(key: key, control: control); + case "MergeSemantics": + return MergeSemanticsControl(key: key, control: control); + case "Column": + return ColumnControl(key: key, control: control); + case "Dismissible": + return DismissibleControl(key: key, control: control); + case "GestureDetector": + return GestureDetectorControl(key: key, control: control); + case "GridView": + return GridViewControl(key: key, control: control); + case "NavigationBar": + return NavigationBarControl(key: key, control: control); + case "NavigationBarDestination": + return NavigationBarDestinationControl(key: key, control: control); + case "CupertinoNavigationBar": + return CupertinoNavigationBarControl(key: key, control: control); + case "CupertinoActionSheet": + return CupertinoActionSheetControl(key: key, control: control); + case "CupertinoActionSheetAction": + return CupertinoActionSheetActionControl(key: key, control: control); + case "Container": + return ContainerControl(key: key, control: control); + case "Chip": + return ChipControl(key: key, control: control); + case "CircleAvatar": + return CircleAvatarControl(key: key, control: control); + case "InteractiveViewer": + return InteractiveViewerControl(key: key, control: control); + case "IconButton": + return IconButtonControl(key: key, control: control); + case "CupertinoActivityIndicator": + return CupertinoActivityIndicatorControl(key: key, control: control); + case "CupertinoAlertDialog": + return CupertinoAlertDialogControl(key: key, control: control); + case "CupertinoAppBar": + return CupertinoAppBarControl(key: key, control: control); + case "CupertinoButton": + case "CupertinoFilledButton": + case "CupertinoTintedButton": + return CupertinoButtonControl(key: key, control: control); + case "CupertinoContextMenu": + return CupertinoContextMenuControl(key: key, control: control); + case "CupertinoContextMenuAction": + return CupertinoContextMenuActionControl(key: key, control: control); + case "CupertinoDialogAction": + return CupertinoDialogActionControl(key: key, control: control); + case "CupertinoSlider": + return CupertinoSliderControl(key: key, control: control); + case "CupertinoSwitch": + return CupertinoSwitchControl(key: key, control: control); + case "DropdownM2": + return DropdownM2Control(key: key, control: control); + case "CupertinoListTile": + return CupertinoListTileControl(key: key, control: control); + case "CupertinoPicker": + return CupertinoPickerControl(key: key, control: control); + case "CupertinoTimerPicker": + return CupertinoTimerPickerControl(key: key, control: control); + case "DataTable": + return DataTableControl(key: key, control: control); + case "AutoComplete": + return AutoCompleteControl(key: key, control: control); + case "DatePicker": + return DatePickerControl(key: key, control: control); + case "Divider": + return DividerControl(key: key, control: control); + case "Dropdown": + return DropdownControl(key: key, control: control); + case "ExpansionPanelList": + return ExpansionPanelListControl(key: key, control: control); + case "ExpansionTile": + return ExpansionTileControl(key: key, control: control); + case "ElevatedButton": + case "FilledButton": + case "FilledTonalButton": + case "TextButton": + case "OutlinedButton": + return AdaptiveButtonControl(key: key, control: control); + case "FletApp": + return FletAppControl(key: key, control: control); + case "SubmenuButton": + return SubmenuButtonControl(key: key, control: control); + case "FloatingActionButton": + return FloatingActionButtonControl(key: key, control: control); + case "Icon": + return IconControl(key: key, control: control); + case "Image": + return ImageControl(key: key, control: control); + case "ListTile": + return ListTileControl(key: key, control: control); + case "ListView": + return ListViewControl(key: key, control: control); + case "TimePicker": + return TimePickerControl(key: key, control: control); + case "TransparentPointer": + return TransparentPointerControl(key: key, control: control); + case "ResponsiveRow": + return ResponsiveRowControl(key: key, control: control); + case "NavigationDrawer": + return NavigationDrawerControl(key: key, control: control); + case "NavigationRail": + return NavigationRailControl(key: key, control: control); + case "ReorderableListView": + return ReorderableListViewControl(key: key, control: control); + case "Page": + return PageControl(key: key, control: control); + case "ProgressBar": + return ProgressBarControl(key: key, control: control); + case "ProgressRing": + return ProgressRingControl(key: key, control: control); + case "ShaderMask": + return ShaderMaskControl(key: key, control: control); + case "RangeSlider": + return RangeSliderControl(key: key, control: control); + case "ReorderableDraggable": + return ReorderableDraggableControl(key: key, control: control); + case "Row": + return RowControl(key: key, control: control); + case "Slider": + return AdaptiveSliderControl(key: key, control: control); + case "SnackBar": + return SnackBarControl(key: key, control: control); + case "Stack": + return StackControl(key: key, control: control); + case "Switch": + return AdaptiveSwitchControl(key: key, control: control); + case "Tabs": + return TabsControl(key: key, control: control); + case "Text": + return TextControl(key: key, control: control); + case "TextField": + return AdaptiveTextFieldControl(key: key, control: control); + case "CupertinoTextField": + return CupertinoTextFieldControl(key: key, control: control); + case "Placeholder": + return PlaceholderControl(key: key, control: control); + case "MenuBar": + return MenuBarControl(key: key, control: control); + case "VerticalDivider": + return VerticalDividerControl(key: key, control: control); + case "MenuItemButton": + return MenuItemButtonControl(key: key, control: control); + case "View": + return ViewControl(key: key, control: control); + case "SafeArea": + return SafeAreaControl(key: key, control: control); + case "SelectionArea": + return SelectionAreaControl(key: key, control: control); + case "RadioGroup": + return RadioGroupControl(key: key, control: control); + case "Radio": + return AdaptiveRadioControl(key: key, control: control); + case "CupertinoRadio": + return CupertinoRadioControl(key: key, control: control); + case "Window": + return WindowControl(key: key, control: control); + case "WindowDragArea": + return WindowDragAreaControl(key: key, control: control); + case "Pagelet": + return PageletControl(key: key, control: control); + default: + return null; + } + } + + @override + FletService? createService(Control control) { + switch (control.type) { + case "BrowserContextMenu": + return BrowserContextMenuService(control: control); + case "Clipboard": + return ClipboardService(control: control); + case "FilePicker": + return FilePickerService(control: control); + case "HapticFeedback": + return HapticFeedbackService(control: control); + case "ShakeDetector": + return ShakeDetectorService(control: control); + case "SharedPreferences": + return SharedPreferencesService(control: control); + case "SemanticsService": + return SemanticsServiceControl(control: control); + case "StoragePaths": + return StoragePaths(control: control); + case "UrlLauncher": + return UrlLauncherService(control: control); + default: + return null; + } + } + + @override + void ensureInitialized() {} +} diff --git a/packages/flet/lib/src/flet_extension.dart b/packages/flet/lib/src/flet_extension.dart new file mode 100644 index 000000000..b6d0e559f --- /dev/null +++ b/packages/flet/lib/src/flet_extension.dart @@ -0,0 +1,16 @@ +import 'package:flutter/widgets.dart'; + +import 'flet_service.dart'; +import 'models/control.dart'; + +abstract class FletExtension { + Widget? createWidget(Key? key, Control control) { + return null; + } + + FletService? createService(Control control) { + return null; + } + + void ensureInitialized() {} +} diff --git a/packages/flet/lib/src/flet_server.dart b/packages/flet/lib/src/flet_server.dart deleted file mode 100644 index 84a4dbd41..000000000 --- a/packages/flet/lib/src/flet_server.dart +++ /dev/null @@ -1,282 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; -import 'package:redux/redux.dart'; - -import 'actions.dart'; -import 'flet_app_errors_handler.dart'; -import 'flet_control_backend.dart'; -import 'flet_server_protocol.dart'; -import 'models/app_state.dart'; -import 'protocol/add_page_controls_payload.dart'; -import 'protocol/app_become_active_payload.dart'; -import 'protocol/app_become_inactive_payload.dart'; -import 'protocol/append_control_props_request.dart'; -import 'protocol/clean_control_payload.dart'; -import 'protocol/invoke_method_payload.dart'; -import 'protocol/message.dart'; -import 'protocol/page_controls_batch_payload.dart'; -import 'protocol/page_event_from_web_request.dart'; -import 'protocol/register_webclient_request.dart'; -import 'protocol/register_webclient_response.dart'; -import 'protocol/remove_control_payload.dart'; -import 'protocol/replace_page_controls_payload.dart'; -import 'protocol/session_crashed_payload.dart'; -import 'protocol/update_control_props_payload.dart'; -import 'protocol/update_control_props_request.dart'; -import 'utils/uri.dart'; - -class FletServer implements FletControlBackend { - final Store _store; - final int? reconnectIntervalMs; - final int? reconnectTimeoutMs; - final FletAppErrorsHandler? errorsHandler; - - FletServerProtocol? _clientProtocol; - bool _disposed = false; - String _address = ""; - String _pageName = ""; - String _pageHash = ""; - String _pageWidth = ""; - String _pageHeight = ""; - String _windowWidth = ""; - String _windowHeight = ""; - String _windowTop = ""; - String _windowLeft = ""; - String _isPWA = ""; - String _isWeb = ""; - String _isDebug = ""; - String _platform = ""; - String _platformBrightness = ""; - String _media = ""; - int reconnectStarted = 0; - final Map controlInvokeMethods; - - FletServer(this._store, this.controlInvokeMethods, - {this.reconnectIntervalMs, this.reconnectTimeoutMs, this.errorsHandler}); - - Future connect({required String address}) async { - _address = address; - - debugPrint("Connecting to Flet server $address..."); - try { - _clientProtocol = FletServerProtocol( - address: _address, - onDisconnect: _onDisconnect, - onMessage: _onMessage); - await _clientProtocol!.connect(); - registerWebClientInternal(); - } catch (e) { - debugPrint("Error connecting to Flet server: $e"); - _onDisconnect(); - } - } - - _onDisconnect() { - if (_disposed) { - return; - } - - var nextReconnectDelayMs = _store.state.reconnectDelayMs; - if (nextReconnectDelayMs == 0) { - reconnectStarted = DateTime.now().millisecondsSinceEpoch; - } - - // set/update timeout - nextReconnectDelayMs = - nextReconnectDelayMs == 0 || _clientProtocol!.isLocalConnection - ? reconnectIntervalMs ?? _clientProtocol!.defaultReconnectIntervalMs - : nextReconnectDelayMs * 2; - - if (reconnectTimeoutMs == null || - (DateTime.now().millisecondsSinceEpoch - reconnectStarted) < - reconnectTimeoutMs!) { - // re-connect - _store.dispatch(PageReconnectingAction( - isUdsPath(_address) ? "" : "Loading...", nextReconnectDelayMs)); - - debugPrint("Reconnect in $nextReconnectDelayMs milliseconds"); - Future.delayed(Duration(milliseconds: nextReconnectDelayMs)) - .then((value) async { - await connect(address: _address); - }); - } else if (reconnectTimeoutMs != null) { - errorsHandler - ?.onError("Error connecting to a Flet service in a timely manner."); - } - } - - registerWebClient( - {required String pageName, - required String pageRoute, - required String pageWidth, - required String pageHeight, - required String windowWidth, - required String windowHeight, - required String windowTop, - required String windowLeft, - required String isPWA, - required String isWeb, - required String isDebug, - required String platform, - required String platformBrightness, - required String media}) { - _pageName = pageName; - _pageHash = pageRoute; - _pageWidth = pageWidth; - _pageHeight = pageHeight; - _windowWidth = windowWidth; - _windowHeight = windowHeight; - _windowTop = windowTop; - _windowLeft = windowLeft; - _isPWA = isPWA; - _isWeb = isWeb; - _isDebug = isDebug; - _platform = platform; - _platformBrightness = platformBrightness; - _media = media; - } - - registerWebClientInternal() { - debugPrint("registerWebClientInternal"); - var page = _store.state.controls["page"]; - send(Message( - action: MessageAction.registerWebClient, - payload: RegisterWebClientRequest( - pageName: _pageName, - pageRoute: _pageHash != "" ? _pageHash : _store.state.route, - pageWidth: page?.attrString("pageWidth") ?? _pageWidth, - pageHeight: page?.attrString("pageHeight") ?? _pageHeight, - windowLeft: page?.attrString("windowLeft") ?? _windowLeft, - windowTop: page?.attrString("windowTop") ?? _windowTop, - windowWidth: page?.attrString("windowWidth") ?? _windowWidth, - windowHeight: page?.attrString("windowHeight") ?? _windowHeight, - isPWA: _isPWA, - isWeb: _isWeb, - isDebug: _isDebug, - platform: _platform, - platformBrightness: _platformBrightness, - media: _media, - sessionId: _store.state.sessionId))); - _pageHash = ""; - } - - @override - void updateControlState(String id, Map props, - {bool client = true, bool server = true}) { - Map allProps = {"i": id}; - for (var entry in props.entries) { - allProps[entry.key.toLowerCase()] = entry.value; - } - if (client) { - _store.dispatch(UpdateControlPropsAction( - UpdateControlPropsPayload(props: [allProps]))); - } - if (server) { - _updateControlProps(props: [allProps]); - } - } - - @override - void triggerControlEvent(String controlId, String eventName, - [String? eventData]) { - _sendPageEvent( - eventTarget: controlId, eventName: eventName, eventData: eventData); - } - - @override - void subscribeMethods(String controlId, - Future Function(String, Map) methodHandler) { - controlInvokeMethods[controlId] = methodHandler; - } - - @override - void unsubscribeMethods(String controlId) { - controlInvokeMethods.remove(controlId); - } - - _sendPageEvent( - {required String eventTarget, - required String eventName, - String? eventData}) { - send(Message( - action: MessageAction.pageEventFromWeb, - payload: PageEventFromWebRequest( - eventTarget: eventTarget, - eventName: eventName, - eventData: eventData))); - } - - _updateControlProps({required List> props}) { - send(Message( - action: MessageAction.updateControlProps, - payload: UpdateControlPropsRequest(props: props))); - } - - _onMessage(message) { - debugPrint("WS message: $message"); - final msg = Message.fromJson(json.decode(message)); - switch (msg.action) { - case MessageAction.registerWebClient: - _store.dispatch(RegisterWebClientAction( - RegisterWebClientResponse.fromJson(msg.payload), this)); - break; - case MessageAction.appBecomeActive: - _store.dispatch(AppBecomeActiveAction( - this, AppBecomeActivePayload.fromJson(msg.payload))); - break; - case MessageAction.appBecomeInactive: - _store.dispatch(AppBecomeInactiveAction( - AppBecomeInactivePayload.fromJson(msg.payload))); - break; - case MessageAction.sessionCrashed: - _store.dispatch( - SessionCrashedAction(SessionCrashedPayload.fromJson(msg.payload))); - break; - case MessageAction.invokeMethod: - _store.dispatch(InvokeMethodAction( - InvokeMethodPayload.fromJson(msg.payload), this)); - break; - case MessageAction.addPageControls: - _store.dispatch(AddPageControlsAction( - AddPageControlsPayload.fromJson(msg.payload))); - break; - case MessageAction.appendControlProps: - _store.dispatch(AppendControlPropsAction( - AppendControlPropsPayload.fromJson(msg.payload))); - break; - case MessageAction.updateControlProps: - _store.dispatch(UpdateControlPropsAction( - UpdateControlPropsPayload.fromJson(msg.payload))); - break; - case MessageAction.replacePageControls: - _store.dispatch(ReplacePageControlsAction( - ReplacePageControlsPayload.fromJson(msg.payload))); - break; - case MessageAction.cleanControl: - _store.dispatch( - CleanControlAction(CleanControlPayload.fromJson(msg.payload))); - break; - case MessageAction.removeControl: - _store.dispatch( - RemoveControlAction(RemoveControlPayload.fromJson(msg.payload))); - break; - case MessageAction.pageControlsBatch: - _store.dispatch(PageControlsBatchAction( - PageControlsBatchPayload.fromJson(msg.payload))); - break; - default: - } - } - - send(Message message) { - final m = json.encode(message.toJson()); - _clientProtocol?.send(m); - } - - void disconnect() { - debugPrint("Disconnecting from Flet server."); - _disposed = true; - _clientProtocol?.disconnect(); - } -} diff --git a/packages/flet/lib/src/flet_server_protocol.dart b/packages/flet/lib/src/flet_server_protocol.dart deleted file mode 100644 index 18c7b8b90..000000000 --- a/packages/flet/lib/src/flet_server_protocol.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'flet_server_protocol_javascript_io.dart' - if (dart.library.js) "flet_server_protocol_javascript_web.dart"; -import 'flet_server_protocol_tcp_socket.dart'; -import 'flet_server_protocol_web_socket.dart'; -import 'utils/platform_utils_non_web.dart' - if (dart.library.js) "utils/platform_utils_web.dart"; - -typedef FletServerProtocolOnDisconnectCallback = void Function(); -typedef FletServerProtocolOnMessageCallback = void Function(String message); -typedef ControlInvokeMethodCallback = Future Function( - String methodName, Map args); - -abstract class FletServerProtocol { - factory FletServerProtocol( - {required String address, - required FletServerProtocolOnDisconnectCallback onDisconnect, - required FletServerProtocolOnMessageCallback onMessage}) { - if (isFletWebPyodideMode()) { - // JavaScript - return FletJavaScriptServerProtocol( - address: address, onDisconnect: onDisconnect, onMessage: onMessage); - } else if (address.startsWith("http://") || - address.startsWith("https://")) { - // WebSocket - return FletWebSocketServerProtocol( - address: address, onDisconnect: onDisconnect, onMessage: onMessage); - } else { - // TCP or UDS - return FletTcpSocketServerProtocol( - address: address, onDisconnect: onDisconnect, onMessage: onMessage); - } - } - - Future connect(); - bool get isLocalConnection; - int get defaultReconnectIntervalMs; - void send(String message); - void disconnect(); -} diff --git a/packages/flet/lib/src/flet_server_protocol_javascript_io.dart b/packages/flet/lib/src/flet_server_protocol_javascript_io.dart deleted file mode 100644 index 6a536685a..000000000 --- a/packages/flet/lib/src/flet_server_protocol_javascript_io.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'flet_server_protocol.dart'; - -class FletJavaScriptServerProtocol implements FletServerProtocol { - final String address; - final FletServerProtocolOnMessageCallback onMessage; - final FletServerProtocolOnDisconnectCallback onDisconnect; - - FletJavaScriptServerProtocol( - {required this.address, - required this.onDisconnect, - required this.onMessage}); - - @override - connect() async {} - - @override - bool get isLocalConnection => true; - - @override - int get defaultReconnectIntervalMs => 10; - - @override - void send(String message) {} - - @override - void disconnect() {} -} diff --git a/packages/flet/lib/src/flet_server_protocol_javascript_web.dart b/packages/flet/lib/src/flet_server_protocol_javascript_web.dart deleted file mode 100644 index 77224ea8c..000000000 --- a/packages/flet/lib/src/flet_server_protocol_javascript_web.dart +++ /dev/null @@ -1,46 +0,0 @@ -@JS() -library script.js; - -import 'dart:js_util'; - -import 'package:flutter/foundation.dart'; -import 'package:js/js.dart'; - -import 'flet_server_protocol.dart'; - -@JS() -external dynamic jsConnect(FletServerProtocolOnMessageCallback onMessage); - -@JS() -external dynamic jsSend(String data); - -class FletJavaScriptServerProtocol implements FletServerProtocol { - final String address; - final FletServerProtocolOnMessageCallback onMessage; - final FletServerProtocolOnDisconnectCallback onDisconnect; - - FletJavaScriptServerProtocol( - {required this.address, - required this.onDisconnect, - required this.onMessage}); - - @override - connect() async { - debugPrint("Connecting to JavaScript server $address..."); - await promiseToFuture(jsConnect(allowInterop(onMessage))); - } - - @override - bool get isLocalConnection => true; - - @override - int get defaultReconnectIntervalMs => 10; - - @override - void send(String message) { - jsSend(message); - } - - @override - void disconnect() {} -} diff --git a/packages/flet/lib/src/flet_server_protocol_tcp_socket.dart b/packages/flet/lib/src/flet_server_protocol_tcp_socket.dart deleted file mode 100644 index 6257c23e3..000000000 --- a/packages/flet/lib/src/flet_server_protocol_tcp_socket.dart +++ /dev/null @@ -1,146 +0,0 @@ -import 'dart:convert' show utf8; -import 'dart:io'; -import 'dart:typed_data'; - -import 'package:flutter/foundation.dart'; - -import 'flet_server_protocol.dart'; -import 'utils/networking.dart'; - -const int defaultLocalReconnectInterval = 200; -const int defaultPublicReconnectInterval = 500; - -class FletTcpSocketServerProtocol implements FletServerProtocol { - String address; - FletServerProtocolOnMessageCallback onMessage; - FletServerProtocolOnDisconnectCallback onDisconnect; - Socket? _socket; - late final bool _isLocalConnection; - late final int _defaultReconnectIntervalMs; - - FletTcpSocketServerProtocol( - {required this.address, - required this.onDisconnect, - required this.onMessage}); - - @override - connect() async { - debugPrint("Connecting to Socket server $address..."); - - if (address.startsWith("tcp://")) { - var u = Uri.parse(address); - _isLocalConnection = await isPrivateHost(u.host); - _defaultReconnectIntervalMs = _isLocalConnection - ? defaultLocalReconnectInterval - : defaultPublicReconnectInterval; - _socket = await Socket.connect(u.host, u.port); - debugPrint( - 'Connected to: ${_socket!.remoteAddress.address}:${_socket!.remotePort}'); - } else { - final udsPath = InternetAddress(address, type: InternetAddressType.unix); - _isLocalConnection = true; - _defaultReconnectIntervalMs = defaultLocalReconnectInterval; - _socket = await Socket.connect(udsPath, 0); - debugPrint('Connected to: $udsPath'); - } - - BytesBuilder buffer = BytesBuilder(); - int msgLen = 0; - - // listen for responses from the server - _socket!.listen( - // handle data from the server - (Uint8List data) { - debugPrint("Received packet: ${data.length}"); - int packetLen = data.length; - - // process packet - // it can contain multiple messages - int i = 0; - while (i < packetLen) { - // read message size - if (msgLen == 0) { - //print("Read message size: $i"); - int end = i + (4 - buffer.length); - if (end > packetLen) { - end = packetLen; - } - buffer.add(data.sublist(i, end)); - - i = end; - - if (buffer.length == 4) { - msgLen = ByteData.sublistView(buffer.toBytes()) - .getUint32(0, Endian.big); - //print("Message size: $msgLen"); - buffer.clear(); - } - } - - // read message body - if (msgLen > 0) { - //print("Read message body: $i"); - int end = i + (msgLen - buffer.length); - if (end > packetLen) { - end = packetLen; - } - buffer.add(data.sublist(i, end)); - - i = end; - - if (buffer.length == msgLen) { - String message = String.fromCharCodes(buffer.toBytes()); - debugPrint('Received message: ${message.length}'); - _onMessage(message); - buffer.clear(); - msgLen = 0; - } - } - } - }, - - // handle errors - onError: (error) { - debugPrint("Error: $error"); - _socket?.destroy(); - onDisconnect(); - }, - - // handle server ending connection - onDone: () { - debugPrint('Server left.'); - _socket?.destroy(); - onDisconnect(); - }, - ); - } - - @override - bool get isLocalConnection => _isLocalConnection; - - @override - int get defaultReconnectIntervalMs => _defaultReconnectIntervalMs; - - _onMessage(message) { - onMessage(message); - } - - Uint8List int32bytes(int value) => - Uint8List(4)..buffer.asInt32List()[0] = value; - - Uint8List int32BigEndianBytes(int value) => - Uint8List(4)..buffer.asByteData().setInt32(0, value, Endian.big); - - @override - void send(String message) { - var buffer = utf8.encode(message); - _socket!.add(int32BigEndianBytes(buffer.length)); - debugPrint('Sending: ${buffer.length}'); - _socket!.write(message); - } - - @override - void disconnect() { - _socket?.destroy(); - } -} diff --git a/packages/flet/lib/src/flet_service.dart b/packages/flet/lib/src/flet_service.dart new file mode 100644 index 000000000..55ef8fb14 --- /dev/null +++ b/packages/flet/lib/src/flet_service.dart @@ -0,0 +1,17 @@ +import 'package:flutter/foundation.dart'; + +import 'models/control.dart'; + +abstract class FletService { + Control control; + + FletService({required this.control}); + + @mustCallSuper + void init() {} + + void update() {} + + @mustCallSuper + void dispose() {} +} diff --git a/packages/flet/lib/src/models/app_state.dart b/packages/flet/lib/src/models/app_state.dart deleted file mode 100644 index fdd56415f..000000000 --- a/packages/flet/lib/src/models/app_state.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:flutter/cupertino.dart'; - -import '../protocol/page_media_data.dart'; -import 'control.dart'; - -class Counter { - int value; - Counter(this.value); -} - -class AppState extends Equatable { - final Uri? pageUri; - final String assetsDir; - final String route; - final String deepLinkingRoute; - final String sessionId; - final bool isLoading; - final bool isRegistered; - final int reconnectDelayMs; - final String error; - final Size size; - final Brightness displayBrightness; - final PageMediaData media; - final Map sizeBreakpoints; - final Map controls; - - const AppState( - {required this.pageUri, - required this.assetsDir, - required this.route, - required this.deepLinkingRoute, - required this.sessionId, - required this.isLoading, - required this.isRegistered, - required this.reconnectDelayMs, - required this.error, - required this.size, - required this.sizeBreakpoints, - required this.displayBrightness, - required this.media, - required this.controls}); - - factory AppState.initial() => AppState( - pageUri: null, - assetsDir: "", - route: "", - deepLinkingRoute: "", - sessionId: "", - isLoading: true, - isRegistered: false, - reconnectDelayMs: 0, - error: "", - size: const Size(0, 0), - sizeBreakpoints: const { - "xs": 0, - "sm": 576, - "md": 768, - "lg": 992, - "xl": 1200, - "xxl": 1400 - }, - displayBrightness: Brightness.light, - media: PageMediaData( - padding: PaddingData(EdgeInsets.zero), - viewPadding: PaddingData(EdgeInsets.zero), - viewInsets: PaddingData(EdgeInsets.zero)), - controls: { - "page": Control( - id: "page", - pid: "", - type: "page", - name: "", - childIds: const [], - attrs: const {}) - }); - - AppState copyWith( - {Uri? pageUri, - String? assetsDir, - String? route, - String? deepLinkingRoute, - String? sessionId, - bool? isLoading, - bool? isRegistered, - int? reconnectDelayMs, - String? error, - Size? size, - Map? sizeBreakpoints, - Brightness? displayBrightness, - PageMediaData? media, - Map? controls}) => - AppState( - pageUri: pageUri ?? this.pageUri, - assetsDir: assetsDir ?? this.assetsDir, - route: route ?? this.route, - deepLinkingRoute: deepLinkingRoute ?? this.deepLinkingRoute, - sessionId: sessionId ?? this.sessionId, - isLoading: isLoading ?? this.isLoading, - isRegistered: isRegistered ?? this.isRegistered, - reconnectDelayMs: reconnectDelayMs ?? this.reconnectDelayMs, - error: error ?? this.error, - size: size ?? this.size, - sizeBreakpoints: sizeBreakpoints ?? this.sizeBreakpoints, - displayBrightness: displayBrightness ?? this.displayBrightness, - media: media ?? this.media, - controls: controls ?? this.controls); - - @override - List get props => [isLoading, error, sessionId, controls]; -} diff --git a/packages/flet/lib/src/models/asset_source.dart b/packages/flet/lib/src/models/asset_source.dart new file mode 100644 index 000000000..5227d65a1 --- /dev/null +++ b/packages/flet/lib/src/models/asset_source.dart @@ -0,0 +1,11 @@ +class AssetSource { + final String path; + final bool isFile; + + const AssetSource({required this.path, required this.isFile}); + + @override + String toString() { + return 'AssetSource(path: $path, isFile: $isFile)'; + } +} diff --git a/packages/flet/lib/src/models/asset_src.dart b/packages/flet/lib/src/models/asset_src.dart deleted file mode 100644 index 8f2feaa1e..000000000 --- a/packages/flet/lib/src/models/asset_src.dart +++ /dev/null @@ -1,6 +0,0 @@ -class AssetSrc { - final String path; - final bool isFile; - - const AssetSrc({required this.path, required this.isFile}); -} diff --git a/packages/flet/lib/src/models/control.dart b/packages/flet/lib/src/models/control.dart index 826f99bed..9ef3a7a4e 100644 --- a/packages/flet/lib/src/models/control.dart +++ b/packages/flet/lib/src/models/control.dart @@ -1,165 +1,473 @@ -import 'dart:convert'; +import 'dart:async'; -import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; -import '../utils/colors.dart'; +import '../flet_backend.dart'; -class Control extends Equatable { - static const reservedProps = ['i', 'p', 't', 'c', 'n']; +typedef InvokeControlMethodCallback = Future Function( + String name, dynamic args); - final String id; - final String pid; +enum OperationType { + unknown(-1), + replace(0), + add(1), + remove(2), + move(3); + + final int value; + + const OperationType(this.value); + + static OperationType? fromInt(int value) { + return OperationType.values.firstWhere( + (e) => e.value == value, + orElse: () => unknown, // return unknown if not found + ); + } +} + +class PatchTarget { + final dynamic obj; + final Control control; + + const PatchTarget(this.obj, this.control); +} + +/// Represents a node or control in the UI tree. +/// +/// This class extends `ChangeNotifier`, allowing it to notify listeners +/// whenever any part of its data changes. It uses a unified properties +/// map to store all nested data. Any value (or list element) in the +/// properties map that is a `Map` containing a "_c" key is automatically +/// transformed into a `Control`. +class Control extends ChangeNotifier { + static const DeepCollectionEquality _equality = DeepCollectionEquality(); + final int id; final String type; - final String? name; - final List childIds; - final Map attrs; - final Map state = {}; - final Set onRemove = {}; - - Control( - {required this.id, - required this.pid, - required this.type, - required this.name, - required this.childIds, - required this.attrs}); - - factory Control.fromJson(Map json) { - Map attrs = {}; - for (var key in json.keys) { - if (!reservedProps.contains(key)) { - attrs[key] = json[key] as String; - } + final Map properties; + bool notifyParent = false; + final List _notifyParentProperties = ["visible"]; + WeakReference? _parent; + late final WeakReference _backend; + Completer? _listenerAddedCompleter; + final List _invokeMethodListeners = []; + + Control({ + required this.id, + required this.type, + required this.properties, + required FletBackend backend, + Control? parent, + }) { + if (parent != null) { + _parent = WeakReference(parent); } + _backend = WeakReference(backend); + } + + Control? get parent => _parent?.target; - return Control( - id: json['i'] as String, - pid: json['p'] as String, - type: (json['t'] as String).toLowerCase(), - name: json['n'] as String?, - childIds: List.from(json['c']), - attrs: attrs); + FletBackend get backend => _backend.target!; + + bool get disabled => + properties["disabled"] == true || (parent?.disabled ?? false); + + bool? get adaptive => properties["adaptive"] ?? parent?.adaptive; + + bool get visible => + !properties.containsKey("visible") || properties["visible"]; + + T? get(String propertyName, [T? defaultValue]) { + return properties.containsKey(propertyName) && + properties[propertyName] != null + ? T == double && properties[propertyName] is int + ? properties[propertyName].toDouble() + : T == String + ? properties[propertyName].toString() + : properties[propertyName] + : defaultValue; } - bool get isDisabled { - return attrBool("disabled", false)!; + /// Returns the [Control] for the given [propertyName], or `null` if not found, not a [Control], + /// or not visible when [visibleOnly] is `true` (default). + Control? child(String propertyName, {bool visibleOnly = true}) { + final child = properties[propertyName]; + if (child is! Control) return null; + return (visibleOnly && !child.visible) ? null : child; } - bool? get isAdaptive { - return attrBool("adaptive"); + /// Returns a list of [Control]s from the specified [propertyName]. + /// + /// If [visibleOnly] is `true` (default), only includes visible controls. + /// + /// Returns an empty list if the property is missing or null. + List children(String propertyName, {bool visibleOnly = true}) { + return List.from(properties[propertyName] ?? []) + .where((c) => !visibleOnly || c.visible) + .toList(); } - bool get isVisible { - return attrBool("visible", true)!; + /// Triggers a control event. + /// + /// This method checks if the control has an event handler for the given + /// [eventName] and triggers the event if the application is not in a loading state. + /// + /// - [eventName]: The name of the event to trigger. + /// - [eventData]: Optional data to pass along with the event. + void triggerEvent(String eventName, [dynamic data]) { + return backend.triggerControlEvent(this, eventName, data); } - bool get isNonVisual { - return [ - //"alertdialog", - //"audio", - "banner", - //"bottomsheet", - "clipboard", - "filepicker", - "hapticfeedback", - "shakedetector", - "snackbar" - ].contains(type); + /// Updates the properties of this control. + /// + /// The [props] map contains key-value pairs where the key is the property + /// name and the value is the new value for that property. + /// + /// - [props]: A map of property names and their corresponding new values. + /// - [dart]: A boolean indicating whether to apply the patch in Dart. Defaults to `true`. + /// - [python]: A boolean indicating whether to send the update to the Python backend. Defaults to `true`. + /// - [notify]: A boolean indicating whether to notify listeners after applying the patch. Defaults to `false`. + /// + /// This method is typically used to modify the state of a control dynamically. + void updateProperties(Map props, + {bool dart = true, bool python = true, bool notify = false}) { + return backend.updateControl(id, props, + dart: dart, python: python, notify: notify); } - bool? attrBool(String name, [bool? defValue]) { - var r = attrs[name.toLowerCase()]; - return r != null ? r.toLowerCase() == "true" : defValue; + /// Creates a ControlNode from MessagePack–decoded data. + factory Control.fromMap(Map data, FletBackend backend, + {Control? parent}) { + if (!data.containsKey("_c")) { + throw Exception("Missing _c field in data: $data"); + } + String type = data["_c"]; + int id = data["_i"]; + Map props = {}; + var newControl = Control( + id: id, + type: type, + properties: props, + backend: backend, + parent: parent); + backend.controlsIndex.set(newControl.id, newControl); + data.forEach((key, value) { + if (key == "_i" || key == "_c") return; + props[key] = _transformIfControl(value, newControl, backend); + }); + return newControl; } - String? attrString(String name, [String? defValue]) { - return attrs[name.toLowerCase()] ?? defValue; + bool update(Map props, {bool shouldNotify = false}) { + final changes = []; + _mergeMaps(this, properties, props, changes, ''); + if (changes.isNotEmpty) { + if (shouldNotify) { + notify(); + } + if (changes.any((prop) => _notifyParentProperties.contains(prop))) { + _parent?.target?.notify(); + } + } + return changes.isNotEmpty; } - int? attrInt(String name, [int? defValue]) { - var r = attrs[name.toLowerCase()]; - if (r != null) { - var i = int.tryParse(r); - return i ?? defValue; + void _mergeMaps( + Control? parent, + Map dst, + Map src, + List changes, + String prefix, + ) { + for (var entry in src.entries) { + final key = entry.key; + final fullKey = prefix.isEmpty ? key : '$prefix.$key'; + + if (dst[key] is Map && entry.value is Map) { + _mergeMaps(parent, dst[key], entry.value, changes, fullKey); + } else if (dst[key] is Control && entry.value is Map) { + _mergeMaps(parent, dst[key].properties, entry.value, changes, fullKey); + } else if (dst[key] != entry.value) { + dst[key] = _transformIfControl(entry.value, parent, backend); + changes.add(fullKey); + } } - return defValue; } - double? attrDouble(String name, [double? defValue]) { - var r = attrs[name.toLowerCase()]; - if (r != null && r.toLowerCase() == "inf") { - return double.infinity; - } else if (r != null) { - var i = double.tryParse(r); - return i ?? defValue; + /// + /// Applies a patch (in MessagePack–decoded form) to this ControlNode. + /// It updates nested ControlNodes or plain data structures accordingly. + /// + /// Patch format: + /// patch := [[],, , ...] + /// + /// operation := | | + /// move_operation := [3, , , + /// , ] + /// remove_operation := [2, , ] + /// other_operation := [0|1, , , ] + /// + /// type: + /// Replace = 0 + /// Add = 1 + /// Remove = 2 + /// Move = 3 + /// + /// tree_index := [[0, {"property|position 1": [index, {"property|position 2"}], ...}] + /// + /// Example: + /// [ + /// 0, + /// { + /// "data_series":[ + /// 1, + /// { + /// 0:[ + /// 2, + /// { + /// "data_points":[ + /// 3, + /// { + /// 1:[ + /// 4 + /// ] + /// } + /// ] + /// } + /// ] + /// } + /// ], + /// } + /// ] + /// + /// Tree is converted to a Map with index as a key and Control, + /// or other object, or map, or list, as a value: + /// + /// 0: # root control .applyPatch is called against + /// 1: # "data_series" collection + /// 2: # "data_series[0]" DataSeries control + /// 3: # "data_series[0]["data_points"]" list of datapoints + /// 4: # "data_series[0]["data_points"][1]" DataPoint control + void applyPatch(List patch, FletBackend backend, + {bool shouldNotify = true}) { + debugPrint("Control($id).applyPatch: $patch, shouldNotify = $shouldNotify"); + + if (patch.length < 2) { + throw Exception( + "Patch must be a list with at least 2 elements: tree_index, operation"); + } + + // build map of "to-be-patched" tree nodes + Map treeIndex = {}; + buildTreeIndex(Control control, dynamic obj, List node) { + // node[0] - index + // node[1] - map of child properties or indexes + treeIndex[node[0]] = + PatchTarget(obj is Control ? obj.properties : obj, control); + if (node.length > 1 && node[1] is Map) { + for (var entry in (node[1] as Map).entries) { + // key - property name or list index + // value - child node + dynamic child; + if (obj is Control) { + child = obj.properties[entry.key]; + } else if (obj is Map) { + child = obj[entry.key]; + } else if (obj is List) { + child = obj[entry.key]; + } + if (child is Control) { + control = child; + } + buildTreeIndex(control, child, entry.value); + } + } + } + + buildTreeIndex(this, this, patch[0]); + //debugPrint("TREE INDEX: $treeIndex"); + + // apply patch commands + for (int i = 1; i < patch.length; i++) { + var op = patch[i] as List; + var opType = OperationType.fromInt(op[0]); + if (opType == OperationType.replace) { + // REPLACE + var node = treeIndex[op[1]]!; + var key = op[2]; + var value = op[3]; + node.obj[key] = _transformIfControl(value, node.control, backend); + if (shouldNotify) { + node.control.notify(); + } + if (key is String) { + node.control.notifyParentIfPropertyChanged(key); + } + } else if (opType == OperationType.add) { + // ADD + var node = treeIndex[op[1]]!; + var index = op[2]; + var value = op[3]; + if (node.obj is! List) { + throw Exception("Add operation can be applied to lists only: $op"); + } + node.obj + .insert(index, _transformIfControl(value, node.control, backend)); + if (shouldNotify) { + node.control.notify(); + } + } else if (opType == OperationType.remove) { + // REMOVE + var node = treeIndex[op[1]]!; + var index = op[2]; + if (node.obj is! List) { + throw Exception("Remove operation can be applied to lists only: $op"); + } + node.obj.removeAt(index); + if (shouldNotify) { + node.control.notify(); + } + } else if (opType == OperationType.move) { + // MOVE + var fromNode = treeIndex[op[1]]!; + var fromIndex = op[2]; + var toNode = treeIndex[op[3]]!; + var toIndex = op[4]; + if (fromNode.obj is! List || toNode.obj is! List) { + throw Exception("Move operation can be applied to lists only: $op"); + } + toNode.obj.insert(toIndex, fromNode.obj.removeAt(fromIndex)); + if (shouldNotify) { + if (fromNode.control.id != toNode.control.id) { + fromNode.control.notify(); + toNode.control.notify(); + } else { + toNode.control.notify(); + } + } + } else { + throw Exception("Unknown patch operation: ${op[0]}"); + } } - return defValue; } - DateTime? attrDateTime(String name, [DateTime? defValue]) { - var value = attrs[name.toLowerCase()]; - if (value == null) { - return defValue; + void notify() { + debugPrint("Notify $type($id)"); + if (notifyParent) { + _parent?.target?.notify(); + } else { + notifyListeners(); } - try { - return DateTime.parse(value); - } catch (e) { - return defValue; + } + + void notifyParentIfPropertyChanged(String name) { + if (_notifyParentProperties.contains(name)) { + debugPrint("notifyParentIfPropertyChanged: $type($id).$name"); + _parent?.target?.notify(); + } + } + + static dynamic _transformIfControl( + dynamic value, Control? parent, FletBackend backend) { + //debugPrint("_transformIfControl: $value"); + if (value is Map && value.containsKey("_c")) { + return Control.fromMap(value, backend, parent: parent); + } else if (value is List && + value.isNotEmpty && + value.first is Map && + (value.first as Map).containsKey("_c")) { + return value.map((e) { + if (e is Map) { + return Control.fromMap(e, backend, parent: parent); + } + return e; + }).toList(); } + return value; } - TimeOfDay? attrTime(String name, [TimeOfDay? defValue]) { - var value = attrs[name.toLowerCase()]; - if (value == null) { - return defValue; + addInvokeMethodListener(InvokeControlMethodCallback listener) { + _invokeMethodListeners.add(listener); + + // If someone was waiting for a listener to be added, complete the future + if (_listenerAddedCompleter != null && + !_listenerAddedCompleter!.isCompleted) { + _listenerAddedCompleter!.complete(); + _listenerAddedCompleter = null; } - List splitted = value.split(':'); - return TimeOfDay( - hour: int.parse(splitted[0]), minute: int.parse(splitted[1])); } - Color? attrColor(String name, BuildContext? context, [Color? defValue]) { - return parseColor( - context != null ? Theme.of(context) : null, attrString(name), defValue); + removeInvokeMethodListener(InvokeControlMethodCallback listener) { + _invokeMethodListeners.remove(listener); } - List? attrList(String name, [List? defValue = const []]) { - var l = attrString(name); - if (l == null) { - return defValue; - } else { + Future invokeMethod( + String name, dynamic args, Duration timeout) async { + debugPrint("$type($id).$name($args)"); + + // If no listeners, wait until one is added or timeout occurs + if (_invokeMethodListeners.isEmpty) { + _listenerAddedCompleter = Completer(); + try { - return jsonDecode(l) as List; + await Future.any([ + _listenerAddedCompleter!.future, + Future.delayed( + timeout, + () => throw TimeoutException( + "No invoke method listeners registered within $timeout")), + ]); } catch (e) { - debugPrint("attrList error while parsing $name: $e"); - return defValue; + rethrow; } } - } - Control copyWith( - {String? id, - String? pid, - String? type, - String? name, - List? childIds, - Map? attrs, - Map? state}) { - Control c = Control( - id: id ?? this.id, - pid: pid ?? this.pid, - type: type ?? this.type, - name: name ?? this.name, - childIds: childIds ?? this.childIds, - attrs: attrs ?? this.attrs); - for (var element in this.state.entries) { - c.state[element.key] = element.value; + if (_invokeMethodListeners.isEmpty) { + throw Exception("No invoke method listeners registered."); } - c.onRemove.addAll(onRemove); - return c; + List results = []; + for (var listener in _invokeMethodListeners) { + results.add(await listener(name, args)); + } + return results.length == 1 ? results[0] : results; + } + + Map toMap() { + return Map.fromEntries( + properties.entries.where((e) => e.value != null).map((e) { + if (e.value is Control) { + return MapEntry(e.key, (e.value as Control).toMap()); + } else if (e.value is List && + e.value.isNotEmpty && + e.value.first is Control) { + return MapEntry(e.key, + (e.value as List).map((c) => (c as Control).toMap()).toList()); + } else { + return MapEntry(e.key, e.value); + } + }), + ); + } + + @override + String toString() { + return toMap().toString(); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + other is Control && + other.id == id && + other.type == type && + _equality.equals(other.properties, properties); } @override - List get props => [id, pid, type, name, childIds, attrs]; + int get hashCode => Object.hash( + id, + type, + _equality.hash(properties), + ); } diff --git a/packages/flet/lib/src/models/control_ancestor_view_model.dart b/packages/flet/lib/src/models/control_ancestor_view_model.dart deleted file mode 100644 index 600356cac..000000000 --- a/packages/flet/lib/src/models/control_ancestor_view_model.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:redux/redux.dart'; - -import 'app_state.dart'; -import 'control.dart'; - -class ControlAncestorViewModel extends Equatable { - final Control? ancestor; - - const ControlAncestorViewModel({required this.ancestor}); - - static ControlAncestorViewModel fromStore( - Store store, String id, String ancestorType) { - Control? ancestor; - String controlId = id; - while (true) { - String parentId = store.state.controls[controlId]!.pid; - if (parentId == "") { - break; - } - ancestor = store.state.controls[parentId]!; - if (ancestor.type.toLowerCase() == ancestorType.toLowerCase()) { - break; - } - controlId = ancestor.id; - } - - return ControlAncestorViewModel(ancestor: ancestor); - } - - @override - List get props => [ancestor]; -} diff --git a/packages/flet/lib/src/models/control_tree_view_model.dart b/packages/flet/lib/src/models/control_tree_view_model.dart deleted file mode 100644 index bf1ca41da..000000000 --- a/packages/flet/lib/src/models/control_tree_view_model.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; -import 'package:redux/redux.dart'; - -import 'app_state.dart'; -import 'control.dart'; - -class ControlTreeViewModel extends Equatable { - final Control control; - final List children; - - const ControlTreeViewModel({required this.control, required this.children}); - - static ControlTreeViewModel fromStore( - Store store, Control control) { - return ControlTreeViewModel( - control: control, - children: control.childIds - .map((childId) => store.state.controls[childId]) - .whereNotNull() - .where((c) => c.isVisible) - .map((c) => ControlTreeViewModel.fromStore(store, c)) - .toList()); - } - - @override - List get props => [control, children]; -} diff --git a/packages/flet/lib/src/models/control_view_model.dart b/packages/flet/lib/src/models/control_view_model.dart deleted file mode 100644 index 05b2a0f1f..000000000 --- a/packages/flet/lib/src/models/control_view_model.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; -import 'package:redux/redux.dart'; - -import 'app_state.dart'; -import 'control.dart'; - -class ControlViewModel extends Equatable { - final Control control; - final List children; - final dynamic dispatch; - - const ControlViewModel( - {required this.control, required this.children, required this.dispatch}); - - static ControlViewModel? fromStore(Store store, String id) { - var control = store.state.controls[id]; - return control != null - ? ControlViewModel( - control: control, - children: control.childIds - .map((childId) => store.state.controls[childId]) - .whereNotNull() - .toList(), - dispatch: store.dispatch) - : null; - } - - @override - List get props => [control, children, dispatch]; -} diff --git a/packages/flet/lib/src/models/controls_view_model.dart b/packages/flet/lib/src/models/controls_view_model.dart deleted file mode 100644 index eb3e4cc3b..000000000 --- a/packages/flet/lib/src/models/controls_view_model.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:equatable/equatable.dart'; -import 'package:redux/redux.dart'; - -import 'app_state.dart'; -import 'control_view_model.dart'; - -class ControlsViewModel extends Equatable { - final List controlViews; - - const ControlsViewModel({required this.controlViews}); - - static ControlsViewModel fromStore( - Store store, Iterable ids) { - return ControlsViewModel( - controlViews: ids - .map((id) => ControlViewModel.fromStore(store, id)) - .whereNotNull() - .toList()); - } - - @override - List get props => [controlViews]; -} diff --git a/packages/flet/lib/src/models/keyboard_event.dart b/packages/flet/lib/src/models/keyboard_event.dart new file mode 100644 index 000000000..b689dd4d1 --- /dev/null +++ b/packages/flet/lib/src/models/keyboard_event.dart @@ -0,0 +1,22 @@ +class KeyboardEvent { + final String key; + final bool isShiftPressed; + final bool isControlPressed; + final bool isAltPressed; + final bool isMetaPressed; + + KeyboardEvent( + {required this.key, + required this.isShiftPressed, + required this.isControlPressed, + required this.isAltPressed, + required this.isMetaPressed}); + + Map toMap() => { + 'key': key, + 'shift': isShiftPressed, + 'ctrl': isControlPressed, + 'alt': isAltPressed, + 'meta': isMetaPressed + }; +} diff --git a/packages/flet/lib/src/models/multi_view.dart b/packages/flet/lib/src/models/multi_view.dart new file mode 100644 index 000000000..5c09458fb --- /dev/null +++ b/packages/flet/lib/src/models/multi_view.dart @@ -0,0 +1,16 @@ +import 'dart:ui'; + +class MultiView { + int viewId; + FlutterView flutterView; + Map initialData; + + MultiView( + {required this.viewId, + required this.flutterView, + required this.initialData}); + + Map toMap() { + return {"view_id": viewId, "initial_data": initialData}; + } +} diff --git a/packages/flet/lib/src/models/page_args_model.dart b/packages/flet/lib/src/models/page_args_model.dart deleted file mode 100644 index f2839c1c3..000000000 --- a/packages/flet/lib/src/models/page_args_model.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:redux/redux.dart'; - -import 'app_state.dart'; - -class PageArgsModel extends Equatable { - final Uri? pageUri; - final String assetsDir; - - const PageArgsModel({required this.pageUri, required this.assetsDir}); - - static PageArgsModel fromStore(Store store) { - return PageArgsModel( - pageUri: store.state.pageUri, assetsDir: store.state.assetsDir); - } - - @override - List get props => [pageUri, assetsDir]; -} diff --git a/packages/flet/lib/src/models/page_design.dart b/packages/flet/lib/src/models/page_design.dart new file mode 100644 index 000000000..ed2fd2e39 --- /dev/null +++ b/packages/flet/lib/src/models/page_design.dart @@ -0,0 +1 @@ +enum PageDesign { material, cupertino } diff --git a/packages/flet/lib/src/models/page_load_view_model.dart b/packages/flet/lib/src/models/page_load_view_model.dart deleted file mode 100644 index 6b1385e48..000000000 --- a/packages/flet/lib/src/models/page_load_view_model.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:redux/redux.dart'; - -import 'app_state.dart'; -import 'page_media_view_model.dart'; - -class PageLoadViewModel extends Equatable { - final Uri? pageUri; - final String sessionId; - final PageMediaViewModel sizeViewModel; - - const PageLoadViewModel( - {required this.pageUri, - required this.sessionId, - required this.sizeViewModel}); - - static PageLoadViewModel fromStore(Store store) { - return PageLoadViewModel( - pageUri: store.state.pageUri, - sessionId: store.state.sessionId, - sizeViewModel: PageMediaViewModel.fromStore(store)); - } - - @override - List get props => [pageUri, sizeViewModel]; -} diff --git a/packages/flet/lib/src/models/page_media_view_model.dart b/packages/flet/lib/src/models/page_media_view_model.dart deleted file mode 100644 index 297e29488..000000000 --- a/packages/flet/lib/src/models/page_media_view_model.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'dart:ui'; - -import 'package:equatable/equatable.dart'; -import 'package:redux/redux.dart'; - -import '../protocol/page_media_data.dart'; -import 'app_state.dart'; - -class PageMediaViewModel extends Equatable { - final bool isRegistered; - final Size size; - final Brightness displayBrightness; - final PageMediaData media; - final Function dispatch; - - const PageMediaViewModel( - {required this.isRegistered, - required this.size, - required this.displayBrightness, - required this.media, - required this.dispatch}); - - static PageMediaViewModel fromStore(Store store) { - return PageMediaViewModel( - isRegistered: store.state.isRegistered, - size: store.state.size, - displayBrightness: store.state.displayBrightness, - media: store.state.media, - dispatch: store.dispatch); - } - - @override - List get props => [size, displayBrightness, dispatch, isRegistered]; -} diff --git a/packages/flet/lib/src/models/page_size_view_model.dart b/packages/flet/lib/src/models/page_size_view_model.dart index 29ca278c3..53f0f9059 100644 --- a/packages/flet/lib/src/models/page_size_view_model.dart +++ b/packages/flet/lib/src/models/page_size_view_model.dart @@ -1,9 +1,6 @@ import 'dart:ui'; import 'package:equatable/equatable.dart'; -import 'package:redux/redux.dart'; - -import 'app_state.dart'; class PageSizeViewModel extends Equatable { final Map breakpoints; @@ -11,11 +8,6 @@ class PageSizeViewModel extends Equatable { const PageSizeViewModel({required this.size, required this.breakpoints}); - static PageSizeViewModel fromStore(Store store) { - return PageSizeViewModel( - size: store.state.size, breakpoints: store.state.sizeBreakpoints); - } - @override List get props => [size, breakpoints]; } diff --git a/packages/flet/lib/src/models/window_media_data.dart b/packages/flet/lib/src/models/window_media_data.dart deleted file mode 100644 index b4be457fb..000000000 --- a/packages/flet/lib/src/models/window_media_data.dart +++ /dev/null @@ -1,18 +0,0 @@ -class WindowMediaData { - bool? isMaximized; - bool? isMinimized; - bool? isMinimizable; - bool? isFullScreen; - bool? isResizable; - bool? isMovable; - bool? isClosable; - bool? isAlwaysOnTop; - bool? isFocused; - bool? isPreventClose; - bool? isVisible; - double? width; - double? height; - double? top; - double? left; - double? opacity; -} diff --git a/packages/flet/lib/src/models/window_state.dart b/packages/flet/lib/src/models/window_state.dart new file mode 100644 index 000000000..57a544c7a --- /dev/null +++ b/packages/flet/lib/src/models/window_state.dart @@ -0,0 +1,61 @@ +class WindowState { + bool maximized; + bool minimized; + bool fullScreen; + bool alwaysOnTop; + bool focused; + bool visible; + bool minimizable; + bool maximizable; + bool resizable; + bool preventClose; + bool skipTaskBar; + double width; + double height; + double top; + double left; + double opacity; + + WindowState({ + required this.maximized, + required this.minimized, + required this.fullScreen, + required this.alwaysOnTop, + required this.focused, + required this.visible, + required this.minimizable, + required this.maximizable, + required this.resizable, + required this.preventClose, + required this.skipTaskBar, + required this.width, + required this.height, + required this.top, + required this.left, + required this.opacity, + }); + + Map toMap() => { + 'maximized': maximized, + 'minimized': minimized, + 'full_screen': fullScreen, + 'always_on_top': alwaysOnTop, + 'focused': focused, + 'visible': visible, + 'minimizable': minimizable, + 'maximizable': maximizable, + 'resizable': resizable, + 'prevent_close': preventClose, + 'skip_task_bar': skipTaskBar, + 'width': width, + 'height': height, + 'top': top, + 'left': left, + 'opacity': opacity, + }; + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/packages/flet/lib/src/protocol/add_page_controls_payload.dart b/packages/flet/lib/src/protocol/add_page_controls_payload.dart deleted file mode 100644 index 71cb5c4f7..000000000 --- a/packages/flet/lib/src/protocol/add_page_controls_payload.dart +++ /dev/null @@ -1,15 +0,0 @@ -import '../models/control.dart'; - -class AddPageControlsPayload { - final List trimIDs; - final List controls; - - AddPageControlsPayload({required this.trimIDs, required this.controls}); - - factory AddPageControlsPayload.fromJson(Map json) { - var controlsJson = json['controls'] as List; - final controls = controlsJson.map((j) => Control.fromJson(j)).toList(); - return AddPageControlsPayload( - trimIDs: List.from(json['trimIDs']), controls: controls); - } -} diff --git a/packages/flet/lib/src/protocol/app_become_active_payload.dart b/packages/flet/lib/src/protocol/app_become_active_payload.dart deleted file mode 100644 index 1f1dccf1e..000000000 --- a/packages/flet/lib/src/protocol/app_become_active_payload.dart +++ /dev/null @@ -1,6 +0,0 @@ -class AppBecomeActivePayload { - AppBecomeActivePayload(); - - factory AppBecomeActivePayload.fromJson(Map json) => - AppBecomeActivePayload(); -} diff --git a/packages/flet/lib/src/protocol/app_become_inactive_payload.dart b/packages/flet/lib/src/protocol/app_become_inactive_payload.dart deleted file mode 100644 index 88105869e..000000000 --- a/packages/flet/lib/src/protocol/app_become_inactive_payload.dart +++ /dev/null @@ -1,8 +0,0 @@ -class AppBecomeInactivePayload { - final String message; - - AppBecomeInactivePayload({required this.message}); - - factory AppBecomeInactivePayload.fromJson(Map json) => - AppBecomeInactivePayload(message: json['message'] as String); -} diff --git a/packages/flet/lib/src/protocol/append_control_props_request.dart b/packages/flet/lib/src/protocol/append_control_props_request.dart deleted file mode 100644 index cca6e80fb..000000000 --- a/packages/flet/lib/src/protocol/append_control_props_request.dart +++ /dev/null @@ -1,13 +0,0 @@ -class AppendControlPropsPayload { - final List> props; - - AppendControlPropsPayload({required this.props}); - - factory AppendControlPropsPayload.fromJson(Map json) { - var propsJson = json['props'] as List; - var props = propsJson - .map((propJson) => Map.from(propJson)) - .toList(); - return AppendControlPropsPayload(props: props); - } -} diff --git a/packages/flet/lib/src/protocol/clean_control_payload.dart b/packages/flet/lib/src/protocol/clean_control_payload.dart deleted file mode 100644 index 2db178662..000000000 --- a/packages/flet/lib/src/protocol/clean_control_payload.dart +++ /dev/null @@ -1,8 +0,0 @@ -class CleanControlPayload { - final List ids; - - CleanControlPayload({required this.ids}); - - factory CleanControlPayload.fromJson(Map json) => - CleanControlPayload(ids: List.from(json['ids'])); -} diff --git a/packages/flet/lib/src/protocol/control_event_body.dart b/packages/flet/lib/src/protocol/control_event_body.dart new file mode 100644 index 000000000..aa39f7c66 --- /dev/null +++ b/packages/flet/lib/src/protocol/control_event_body.dart @@ -0,0 +1,11 @@ +class ControlEventBody { + final int target; + final String name; + final dynamic data; + + ControlEventBody( + {required this.target, required this.name, required this.data}); + + Map toMap() => + {'target': target, 'name': name, 'data': data}; +} diff --git a/packages/flet/lib/src/protocol/invoke_method_payload.dart b/packages/flet/lib/src/protocol/invoke_method_payload.dart deleted file mode 100644 index d4e722e72..000000000 --- a/packages/flet/lib/src/protocol/invoke_method_payload.dart +++ /dev/null @@ -1,22 +0,0 @@ -class InvokeMethodPayload { - final String methodId; - final String methodName; - final String controlId; - final Map args; - - InvokeMethodPayload( - {required this.methodId, - required this.methodName, - required this.controlId, - required this.args}); - - factory InvokeMethodPayload.fromJson(Map json) { - return InvokeMethodPayload( - methodId: json["methodId"], - methodName: json['methodName'], - controlId: json["controlId"], - args: json['arguments'] != null - ? Map.from(json['arguments']) - : {}); - } -} diff --git a/packages/flet/lib/src/protocol/invoke_method_request_body.dart b/packages/flet/lib/src/protocol/invoke_method_request_body.dart new file mode 100644 index 000000000..4f4aaa7d1 --- /dev/null +++ b/packages/flet/lib/src/protocol/invoke_method_request_body.dart @@ -0,0 +1,24 @@ +class InvokeMethodRequestBody { + final int controlId; + final String callId; + final String name; + final dynamic args; + final Duration timeout; + + InvokeMethodRequestBody( + {required this.controlId, + required this.callId, + required this.name, + required this.args, + required this.timeout}); + + factory InvokeMethodRequestBody.fromJson(Map json) { + return InvokeMethodRequestBody( + controlId: json["control_id"], + callId: json["call_id"], + name: json['name'], + args: json['args'], + timeout: Duration( + seconds: json.containsKey("timeout") ? json['timeout'] : 10)); + } +} diff --git a/packages/flet/lib/src/protocol/invoke_method_response_body.dart b/packages/flet/lib/src/protocol/invoke_method_response_body.dart new file mode 100644 index 000000000..671467808 --- /dev/null +++ b/packages/flet/lib/src/protocol/invoke_method_response_body.dart @@ -0,0 +1,16 @@ +class InvokeMethodResponseBody { + final int controlId; + final String callId; + final dynamic result; + final String? error; + + InvokeMethodResponseBody( + {required this.controlId, required this.callId, this.result, this.error}); + + Map toMap() => { + 'control_id': controlId, + 'call_id': callId, + 'result': result, + 'error': error + }; +} diff --git a/packages/flet/lib/src/protocol/invoke_method_result.dart b/packages/flet/lib/src/protocol/invoke_method_result.dart deleted file mode 100644 index 8a3cc7883..000000000 --- a/packages/flet/lib/src/protocol/invoke_method_result.dart +++ /dev/null @@ -1,13 +0,0 @@ -class InvokeMethodResult { - final String methodId; - final String? result; - final String? error; - - InvokeMethodResult({required this.methodId, this.result, this.error}); - - Map toJson() => { - 'method_id': methodId, - 'result': result, - 'error': error - }; -} diff --git a/packages/flet/lib/src/protocol/message.dart b/packages/flet/lib/src/protocol/message.dart index 79dca6dad..72d5229f0 100644 --- a/packages/flet/lib/src/protocol/message.dart +++ b/packages/flet/lib/src/protocol/message.dart @@ -1,17 +1,13 @@ enum MessageAction { - registerWebClient, - pageEventFromWeb, - updateControlProps, - appBecomeActive, - appBecomeInactive, - sessionCrashed, - invokeMethod, - addPageControls, - replacePageControls, - pageControlsBatch, - appendControlProps, - cleanControl, - removeControl + registerClient(1), + patchControl(2), + controlEvent(3), + updateControl(4), + invokeControlMethod(5), + sessionCrashed(6); + + final int value; + const MessageAction(this.value); } class Message { @@ -20,13 +16,11 @@ class Message { Message({required this.action, required this.payload}); - Map toJson() => - {'action': action.name, 'payload': payload.toJson()}; + dynamic toList() => [action.value, payload]; - factory Message.fromJson(Map json) { + factory Message.fromList(List value) { return Message( - action: MessageAction.values - .firstWhere((e) => e.name == json['action'] as String), - payload: json['payload']); + action: MessageAction.values.firstWhere((e) => e.value == value[0]), + payload: value[1]); } } diff --git a/packages/flet/lib/src/protocol/page_controls_batch_payload.dart b/packages/flet/lib/src/protocol/page_controls_batch_payload.dart deleted file mode 100644 index e6e775c71..000000000 --- a/packages/flet/lib/src/protocol/page_controls_batch_payload.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'message.dart'; - -class PageControlsBatchPayload { - final List messages; - - PageControlsBatchPayload({required this.messages}); - - factory PageControlsBatchPayload.fromJson(dynamic json) { - var messagesJson = json as List; - return PageControlsBatchPayload( - messages: messagesJson.map((m) => Message.fromJson(m)).toList()); - } -} diff --git a/packages/flet/lib/src/protocol/page_event_from_web_request.dart b/packages/flet/lib/src/protocol/page_event_from_web_request.dart deleted file mode 100644 index fd54c7135..000000000 --- a/packages/flet/lib/src/protocol/page_event_from_web_request.dart +++ /dev/null @@ -1,16 +0,0 @@ -class PageEventFromWebRequest { - final String eventTarget; - final String eventName; - final String? eventData; - - PageEventFromWebRequest( - {required this.eventTarget, - required this.eventName, - required this.eventData}); - - Map toJson() => { - 'eventTarget': eventTarget, - 'eventName': eventName, - 'eventData': eventData - }; -} diff --git a/packages/flet/lib/src/protocol/page_media_data.dart b/packages/flet/lib/src/protocol/page_media_data.dart index c164dc454..49088e24b 100644 --- a/packages/flet/lib/src/protocol/page_media_data.dart +++ b/packages/flet/lib/src/protocol/page_media_data.dart @@ -11,10 +11,10 @@ class PageMediaData extends Equatable { required this.viewPadding, required this.viewInsets}); - Map toJson() => { - 'padding': padding, - 'view_padding': viewPadding, - 'view_insets': viewInsets, + Map toMap() => { + 'padding': padding.toMap(), + 'view_padding': viewPadding.toMap(), + 'view_insets': viewInsets.toMap(), }; @override @@ -33,7 +33,7 @@ class PaddingData extends Equatable { bottom = insets.bottom, left = insets.left; - Map toJson() => { + Map toMap() => { 'top': top, 'right': right, 'bottom': bottom, diff --git a/packages/flet/lib/src/protocol/patch_control_request_body.dart b/packages/flet/lib/src/protocol/patch_control_request_body.dart new file mode 100644 index 000000000..a96ca928f --- /dev/null +++ b/packages/flet/lib/src/protocol/patch_control_request_body.dart @@ -0,0 +1,16 @@ +class PatchControlRequestBody { + final int id; + final List patch; + + PatchControlRequestBody({ + required this.id, + required this.patch, + }); + + factory PatchControlRequestBody.fromJson(Map json) { + return PatchControlRequestBody( + id: json["id"], + patch: List.from(json['patch']), + ); + } +} diff --git a/packages/flet/lib/src/protocol/register_client_request_body.dart b/packages/flet/lib/src/protocol/register_client_request_body.dart new file mode 100644 index 000000000..d12b49944 --- /dev/null +++ b/packages/flet/lib/src/protocol/register_client_request_body.dart @@ -0,0 +1,14 @@ +class RegisterClientRequestBody { + final String? sessionId; + final String pageName; + final Map page; + + RegisterClientRequestBody( + {required this.sessionId, required this.pageName, required this.page}); + + Map toMap() => { + 'session_id': sessionId, + 'page_name': pageName, + 'page': page + }; +} diff --git a/packages/flet/lib/src/protocol/register_client_response_body.dart b/packages/flet/lib/src/protocol/register_client_response_body.dart new file mode 100644 index 000000000..928f8e514 --- /dev/null +++ b/packages/flet/lib/src/protocol/register_client_response_body.dart @@ -0,0 +1,15 @@ +class RegisterClientResponseBody { + final String? sessionId; + final Map patch; + final String? error; + + RegisterClientResponseBody( + {required this.sessionId, required this.patch, required this.error}); + + factory RegisterClientResponseBody.fromJson(Map json) { + return RegisterClientResponseBody( + sessionId: json["session_id"], + patch: Map.from(json["page_patch"]), + error: json['error'] as String?); + } +} diff --git a/packages/flet/lib/src/protocol/register_webclient_request.dart b/packages/flet/lib/src/protocol/register_webclient_request.dart deleted file mode 100644 index 0a629d988..000000000 --- a/packages/flet/lib/src/protocol/register_webclient_request.dart +++ /dev/null @@ -1,52 +0,0 @@ -class RegisterWebClientRequest { - final String pageName; - final String? pageRoute; - final String? pageWidth; - final String? pageHeight; - final String? windowWidth; - final String? windowHeight; - final String? windowTop; - final String? windowLeft; - final String? isPWA; - final String? isWeb; - final String? isDebug; - final String? platform; - final String? platformBrightness; - final String? media; - final String? sessionId; - - RegisterWebClientRequest( - {required this.pageName, - this.pageRoute, - this.pageWidth, - this.pageHeight, - this.windowWidth, - this.windowHeight, - this.windowTop, - this.windowLeft, - this.isPWA, - this.isWeb, - this.isDebug, - this.platform, - this.platformBrightness, - this.media, - this.sessionId}); - - Map toJson() => { - 'pageName': pageName, - 'pageRoute': pageRoute, - 'pageWidth': pageWidth, - 'pageHeight': pageHeight, - 'windowWidth': windowWidth, - 'windowHeight': windowHeight, - 'windowTop': windowTop, - 'windowLeft': windowLeft, - 'isPWA': isPWA, - 'isWeb': isWeb, - 'isDebug': isDebug, - 'platform': platform, - 'platformBrightness': platformBrightness, - 'media': media, - 'sessionId': sessionId - }; -} diff --git a/packages/flet/lib/src/protocol/register_webclient_response.dart b/packages/flet/lib/src/protocol/register_webclient_response.dart deleted file mode 100644 index df34d4cdf..000000000 --- a/packages/flet/lib/src/protocol/register_webclient_response.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'session_payload.dart'; - -class RegisterWebClientResponse { - final SessionPayload? session; - final bool appInactive; - final String? error; - - RegisterWebClientResponse( - {this.session, required this.appInactive, this.error}); - - factory RegisterWebClientResponse.fromJson(Map json) { - return RegisterWebClientResponse( - session: json['session'] != null - ? SessionPayload.fromJson(json['session']) - : null, - appInactive: json['appInactive'] as bool, - error: json['error'] as String?); - } -} diff --git a/packages/flet/lib/src/protocol/remove_control_payload.dart b/packages/flet/lib/src/protocol/remove_control_payload.dart deleted file mode 100644 index b70226cac..000000000 --- a/packages/flet/lib/src/protocol/remove_control_payload.dart +++ /dev/null @@ -1,8 +0,0 @@ -class RemoveControlPayload { - final List ids; - - RemoveControlPayload({required this.ids}); - - factory RemoveControlPayload.fromJson(Map json) => - RemoveControlPayload(ids: List.from(json['ids'] ?? [])); -} diff --git a/packages/flet/lib/src/protocol/replace_page_controls_payload.dart b/packages/flet/lib/src/protocol/replace_page_controls_payload.dart deleted file mode 100644 index 73dc4001e..000000000 --- a/packages/flet/lib/src/protocol/replace_page_controls_payload.dart +++ /dev/null @@ -1,19 +0,0 @@ -import '../models/control.dart'; - -class ReplacePageControlsPayload { - final List ids; - final List controls; - final bool remove; - - ReplacePageControlsPayload( - {required this.ids, required this.controls, required this.remove}); - - factory ReplacePageControlsPayload.fromJson(Map json) { - var controlsJson = json['controls'] as List; - final controls = controlsJson.map((j) => Control.fromJson(j)).toList(); - return ReplacePageControlsPayload( - ids: List.from(json['ids']), - controls: controls, - remove: json['remove'] as bool); - } -} diff --git a/packages/flet/lib/src/protocol/session_crashed_body.dart b/packages/flet/lib/src/protocol/session_crashed_body.dart new file mode 100644 index 000000000..cbd3ccd4f --- /dev/null +++ b/packages/flet/lib/src/protocol/session_crashed_body.dart @@ -0,0 +1,8 @@ +class SessionCrashedBody { + final String message; + + SessionCrashedBody({required this.message}); + + factory SessionCrashedBody.fromJson(Map json) => + SessionCrashedBody(message: json['message'] as String); +} diff --git a/packages/flet/lib/src/protocol/session_crashed_payload.dart b/packages/flet/lib/src/protocol/session_crashed_payload.dart deleted file mode 100644 index d9b6911cb..000000000 --- a/packages/flet/lib/src/protocol/session_crashed_payload.dart +++ /dev/null @@ -1,8 +0,0 @@ -class SessionCrashedPayload { - final String message; - - SessionCrashedPayload({required this.message}); - - factory SessionCrashedPayload.fromJson(Map json) => - SessionCrashedPayload(message: json['message'] as String); -} diff --git a/packages/flet/lib/src/protocol/session_payload.dart b/packages/flet/lib/src/protocol/session_payload.dart index fdd58f53f..1fbddd2c5 100644 --- a/packages/flet/lib/src/protocol/session_payload.dart +++ b/packages/flet/lib/src/protocol/session_payload.dart @@ -1,16 +1,14 @@ -import '../models/control.dart'; - class SessionPayload { final String id; - final Map controls; + //final Map controls; - SessionPayload({required this.id, required this.controls}); + SessionPayload({required this.id}); - factory SessionPayload.fromJson(Map json) { - Map controls = {}; - for (var key in json['controls'].keys) { - controls[key] = Control.fromJson(json['controls'][key]); - } - return SessionPayload(id: json['id'] as String, controls: controls); - } + // factory SessionPayload.fromJson(Map json) { + // Map controls = {}; + // for (var key in json['controls'].keys) { + // controls[key] = Control.fromJson(json['controls'][key]); + // } + // return SessionPayload(id: json['id'] as String, controls: controls); + // } } diff --git a/packages/flet/lib/src/protocol/update_control_body.dart b/packages/flet/lib/src/protocol/update_control_body.dart new file mode 100644 index 000000000..8bb33409b --- /dev/null +++ b/packages/flet/lib/src/protocol/update_control_body.dart @@ -0,0 +1,8 @@ +class UpdateControlBody { + final int id; + final Map props; + + UpdateControlBody({required this.id, required this.props}); + + Map toMap() => {'id': id, 'props': props}; +} diff --git a/packages/flet/lib/src/protocol/update_control_props_payload.dart b/packages/flet/lib/src/protocol/update_control_props_payload.dart deleted file mode 100644 index bc21d1fe7..000000000 --- a/packages/flet/lib/src/protocol/update_control_props_payload.dart +++ /dev/null @@ -1,13 +0,0 @@ -class UpdateControlPropsPayload { - final List> props; - - UpdateControlPropsPayload({required this.props}); - - factory UpdateControlPropsPayload.fromJson(Map json) { - var propsJson = json['props'] as List; - var props = propsJson - .map((propJson) => Map.from(propJson)) - .toList(); - return UpdateControlPropsPayload(props: props); - } -} diff --git a/packages/flet/lib/src/protocol/update_control_props_request.dart b/packages/flet/lib/src/protocol/update_control_props_request.dart deleted file mode 100644 index 1fd520ee0..000000000 --- a/packages/flet/lib/src/protocol/update_control_props_request.dart +++ /dev/null @@ -1,7 +0,0 @@ -class UpdateControlPropsRequest { - final List> props; - - UpdateControlPropsRequest({required this.props}); - - Map toJson() => {'props': props}; -} diff --git a/packages/flet/lib/src/reducers.dart b/packages/flet/lib/src/reducers.dart deleted file mode 100644 index fead8e084..000000000 --- a/packages/flet/lib/src/reducers.dart +++ /dev/null @@ -1,553 +0,0 @@ -import 'dart:convert'; - -import 'package:flet/src/utils/browser_context_menu.dart'; -import 'package:flutter/foundation.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:device_info_plus/device_info_plus.dart'; - -import 'actions.dart'; -import 'flet_control_backend.dart'; -import 'models/app_state.dart'; -import 'models/control.dart'; -import 'models/window_media_data.dart'; -import 'protocol/add_page_controls_payload.dart'; -import 'protocol/clean_control_payload.dart'; -import 'protocol/invoke_method_result.dart'; -import 'protocol/message.dart'; -import 'protocol/remove_control_payload.dart'; -import 'protocol/update_control_props_payload.dart'; -import 'utils/client_storage.dart'; -import 'utils/clipboard.dart'; -import 'utils/desktop.dart'; -import 'utils/launch_url.dart'; -import 'utils/platform_utils_non_web.dart' - if (dart.library.js) "utils/platform_utils_web.dart"; -import 'utils/session_store_non_web.dart' - if (dart.library.js) "utils/session_store_web.dart"; -import 'utils/uri.dart'; - -enum Actions { increment, setText, setError } - -AppState appReducer(AppState state, dynamic action) { - if (action is PageLoadAction) { - var sessionId = SessionStore.sessionId; - return state.copyWith( - pageUri: action.pageUri, - assetsDir: action.assetsDir, - sessionId: sessionId, - isLoading: true); - } else if (action is PageSizeChangeAction) { - // - // page size changed - // - debugPrint("New page size: ${action.newPageSize}"); - - var page = state.controls["page"]; - var controls = Map.of(state.controls); - if (page != null && !state.isLoading) { - var pageAttrs = Map.of(page.attrs); - pageAttrs["width"] = action.newPageSize.width.toString(); - pageAttrs["height"] = action.newPageSize.height.toString(); - - Map props = { - "width": action.newPageSize.width.toString(), - "height": action.newPageSize.height.toString() - }; - - if (action.wmd != null) { - addWindowMediaEventProps(action.wmd!, pageAttrs); - addWindowMediaEventProps(action.wmd!, props); - } - controls[page.id] = page.copyWith(attrs: pageAttrs); - action.backend.updateControlState("page", props, client: false); - action.backend.triggerControlEvent( - "page", - "resized", - jsonEncode({ - "width": action.newPageSize.width, - "height": action.newPageSize.height - })); - } - - return state.copyWith( - isRegistered: true, controls: controls, size: action.newPageSize); - } else if (action is SetPageRouteAction) { - // - // page route changed - // - var page = state.controls["page"]; - var controls = Map.of(state.controls); - String? deepLinkingRoute; - - if (page != null) { - var pageAttrs = Map.of(page.attrs); - pageAttrs["route"] = action.route; - controls[page.id] = page.copyWith(attrs: pageAttrs); - - if (state.route == "" && state.isLoading) { - // registering a client - debugPrint("Registering web client with route: ${action.route}"); - String pageName = getWebPageName(state.pageUri!); - - getWindowMediaData().then((wmd) async { - String platformValue = defaultTargetPlatform.name.toLowerCase(); - if (platformValue == "android") { - try { - DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); - AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; - if (androidInfo.systemFeatures - .contains('android.software.leanback')) { - platformValue = "android_tv"; - } - } on Exception catch (e) { - debugPrint(e.toString()); - } - } - action.server.registerWebClient( - pageName: pageName, - pageRoute: action.route, - pageWidth: state.size.width.toString(), - pageHeight: state.size.height.toString(), - windowWidth: wmd.width != null ? wmd.width.toString() : "", - windowHeight: wmd.height != null ? wmd.height.toString() : "", - windowTop: wmd.top != null ? wmd.top.toString() : "", - windowLeft: wmd.left != null ? wmd.left.toString() : "", - isPWA: isProgressiveWebApp().toString(), - isWeb: kIsWeb.toString(), - isDebug: kDebugMode.toString(), - platform: platformValue, - platformBrightness: state.displayBrightness.name.toString(), - media: json.encode(state.media)); - - action.server.connect(address: state.pageUri!.toString()); - }); - } else if (state.isLoading) { - // buffer route - deepLinkingRoute = action.route; - } else { - // existing route change - debugPrint("New page route: ${action.route}"); - sendRouteChangeEvent(action.server, action.route); - } - } - - return state.copyWith( - controls: controls, - route: action.route, - deepLinkingRoute: deepLinkingRoute); - } else if (action is WindowEventAction) { - // - // window event - // - - debugPrint("Window event: ${action.eventName}"); - - var page = state.controls["page"]; - var controls = Map.of(state.controls); - if (page != null && !state.isLoading) { - var pageAttrs = Map.of(page.attrs); - Map props = {}; - addWindowMediaEventProps(action.wmd, pageAttrs); - addWindowMediaEventProps(action.wmd, props); - - controls[page.id] = page.copyWith(attrs: pageAttrs); - action.backend.updateControlState("page", props, client: false); - action.backend - .triggerControlEvent("page", "window_event", action.eventName); - } - - return state.copyWith(controls: controls); - } else if (action is PageBrightnessChangeAction) { - // - // platform brightness changed - // - debugPrint("New platform brightness: ${action.brightness.name}"); - - var page = state.controls["page"]; - var controls = Map.of(state.controls); - if (page != null && !state.isLoading) { - var pageAttrs = Map.of(page.attrs); - pageAttrs["platformBrightness"] = action.brightness.name.toString(); - - controls[page.id] = page.copyWith(attrs: pageAttrs); - action.backend.updateControlState( - "page", {"platformBrightness": action.brightness.name.toString()}, - client: false); - action.backend.triggerControlEvent("page", "platformBrightnessChange", - action.brightness.name.toString()); - } - return state.copyWith(displayBrightness: action.brightness); - } else if (action is PageMediaChangeAction) { - // - // page media changed - // - debugPrint("New page media: ${action.media}"); - - var page = state.controls["page"]; - var controls = Map.of(state.controls); - if (page != null && !state.isLoading) { - var pageAttrs = Map.of(page.attrs); - var mj = json.encode(action.media); - pageAttrs["media"] = mj; - - controls[page.id] = page.copyWith(attrs: pageAttrs); - action.backend.updateControlState("page", {"media": mj}, client: false); - action.backend.triggerControlEvent("page", "mediaChange", mj); - } - return state.copyWith(media: action.media); - } else if (action is RegisterWebClientAction) { - // - // register web client - // - if (action.payload.error != null && action.payload.error!.isNotEmpty) { - // error or inactive app - return state.copyWith( - isLoading: action.payload.appInactive, - reconnectDelayMs: 0, - error: action.payload.error); - } else { - final sessionId = action.payload.session!.id; - - // store sessionId in a cookie - SessionStore.sessionId = sessionId; - - if (state.deepLinkingRoute != "") { - debugPrint( - "Sending buffered deep link route: ${state.deepLinkingRoute}"); - sendRouteChangeEvent(action.backend, state.deepLinkingRoute); - } - - // connected to the session - return state.copyWith( - isLoading: false, - deepLinkingRoute: "", - reconnectDelayMs: 0, - sessionId: sessionId, - error: "", - controls: action.payload.session!.controls); - } - } else if (action is PageReconnectingAction) { - // - // reconnecting WebSocket - // - return state.copyWith( - isLoading: true, - error: "", //action.connectMessage, - reconnectDelayMs: action.nextReconnectDelayMs); - } else if (action is AppBecomeActiveAction) { - // - // app become active - // - action.server.registerWebClientInternal(); - return state.copyWith(error: ""); - } else if (action is AppBecomeInactiveAction) { - // - // app become inactive - // - return state.copyWith(isLoading: true, error: ""); - } else if (action is SessionCrashedAction) { - // - // session crashed - // - return state.copyWith(error: action.payload.message); - } else if (action is InvokeMethodAction) { - debugPrint( - "InvokeMethodAction: ${action.payload.methodName} (controlId: ${action.payload.controlId}) (${action.payload.args})"); - - sendMethodResult({String? result, String? error}) { - action.server.triggerControlEvent( - "page", - "invoke_method_result", - json.encode(InvokeMethodResult( - methodId: action.payload.methodId, - result: result, - error: error))); - } - - if (action.payload.controlId != "") { - // control-specific method - var handler = - action.server.controlInvokeMethods[action.payload.controlId]; - debugPrint("Invoke method handler: $handler"); - if (handler != null) { - handler(action.payload.methodName, action.payload.args) - .then((result) => sendMethodResult(result: result.toString())) - .onError((error, stackTrace) => - sendMethodResult(error: error.toString())); - } - } else { - // global methods - switch (action.payload.methodName) { - case "closeInAppWebView": - closeInAppWebView(); - break; - case "launchUrl": - openWebBrowser(action.payload.args["url"]!, - webWindowName: action.payload.args["web_window_name"], - webPopupWindow: - action.payload.args["web_popup_window"]?.toLowerCase() == - "true", - windowWidth: - int.tryParse(action.payload.args["window_width"] ?? ""), - windowHeight: - int.tryParse(action.payload.args["window_height"] ?? "")); - break; - case "canLaunchUrl": - canLaunchUrl(Uri.parse(action.payload.args["url"]!)) - .then((result) => sendMethodResult(result: result.toString())); - break; - case "setClipboard": - String? data = action.payload.args["data"]; - if (data != null) { - try { - setClipboard(data); - } catch (e) { - sendMethodResult(error: e.toString()); - } - } - break; - case "getClipboard": - getClipboard() - .then((value) => sendMethodResult(result: value)) - .onError((error, stackTrace) => - sendMethodResult(error: error?.toString())); - break; - case "enableBrowserContextMenu": - enableBrowserContextMenu().onError((error, stackTrace) => - sendMethodResult(error: error?.toString())); - break; - case "disableBrowserContextMenu": - disableBrowserContextMenu().onError((error, stackTrace) => - sendMethodResult(error: error?.toString())); - break; - case "windowToFront": - windowToFront(); - break; - case "windowStartDragging": - windowStartDragging(); - break; - } - var clientStoragePrefix = "clientStorage:"; - if (action.payload.methodName.startsWith(clientStoragePrefix)) { - invokeClientStorage( - action.payload.methodId, - action.payload.methodName.substring(clientStoragePrefix.length), - action.payload.args, - action.server); - } - } - } else if (action is AddPageControlsAction) { - // - // add controls - // - var controls = Map.of(state.controls); - addControls(controls, action.payload.controls); - removeControls(controls, action.payload.trimIDs); - return state.copyWith(controls: controls); - } else if (action is ReplacePageControlsAction) { - // - // replace controls - // - var controls = Map.of(state.controls); - if (action.payload.remove) { - removeControls(controls, action.payload.ids); - } else { - cleanControls(controls, action.payload.ids); - } - addControls(controls, action.payload.controls); - return state.copyWith(controls: controls); - } else if (action is PageControlsBatchAction) { - // - // batch of commands - // - var controls = Map.of(state.controls); - for (var message in action.payload.messages) { - if (message.action == MessageAction.addPageControls) { - var payload = AddPageControlsPayload.fromJson(message.payload); - addControls(controls, payload.controls); - removeControls(controls, payload.trimIDs); - } else if (message.action == MessageAction.updateControlProps) { - var payload = UpdateControlPropsPayload.fromJson(message.payload); - changeProps(controls, payload.props); - } else if (message.action == MessageAction.cleanControl) { - var payload = CleanControlPayload.fromJson(message.payload); - cleanControls(controls, payload.ids); - } else if (message.action == MessageAction.removeControl) { - var payload = RemoveControlPayload.fromJson(message.payload); - removeControls(controls, payload.ids); - } - } - return state.copyWith(controls: controls); - } else if (action is UpdateControlPropsAction) { - // - // update control props - // - var controls = Map.of(state.controls); - changeProps(controls, action.payload.props); - return state.copyWith(controls: controls); - } else if (action is AppendControlPropsAction) { - // - // append control props - // - var controls = Map.of(state.controls); - appendProps(controls, action.payload.props); - return state.copyWith(controls: controls); - } else if (action is CleanControlAction) { - // - // clean controls - // - var controls = Map.of(state.controls); - cleanControls(controls, action.payload.ids); - return state.copyWith(controls: controls); - } else if (action is RemoveControlAction) { - // - // remove controls - // - var controls = Map.of(state.controls); - removeControls(controls, action.payload.ids); - return state.copyWith(controls: controls); - } - - return state; -} - -addWindowMediaEventProps(WindowMediaData wmd, Map props) { - props["windowwidth"] = wmd.width != null ? wmd.width.toString() : ""; - props["windowheight"] = wmd.height != null ? wmd.height.toString() : ""; - props["windowtop"] = wmd.top != null ? wmd.top.toString() : ""; - props["windowleft"] = wmd.left != null ? wmd.left.toString() : ""; - props["windowminimized"] = - wmd.isMinimized != null ? wmd.isMinimized.toString() : ""; - props["windowmaximized"] = - wmd.isMaximized != null ? wmd.isMaximized.toString() : ""; - props["windowfocused"] = - wmd.isFocused != null ? wmd.isFocused.toString() : ""; - props["windowfullscreen"] = - wmd.isFullScreen != null ? wmd.isFullScreen.toString() : ""; -} - -addControls(Map controls, List newControls) { - String firstParentId = ""; - for (var ctrl in newControls) { - if (firstParentId == "") { - firstParentId = ctrl.pid; - } - - final existingControl = controls[ctrl.id]; - controls[ctrl.id] = ctrl; - if (existingControl != null) { - controls[ctrl.id] = - ctrl.copyWith(childIds: List.from(existingControl.childIds)); - } - - if (ctrl.pid == firstParentId && existingControl == null) { - // root control - final parentCtrl = controls[ctrl.pid]!; - if (ctrl.attrs["at"] == null) { - // append to the end - controls[parentCtrl.id] = parentCtrl.copyWith( - childIds: List.from(parentCtrl.childIds)..add(ctrl.id)); - } else { - // insert at specified position - controls[parentCtrl.id] = parentCtrl.copyWith( - childIds: List.from(parentCtrl.childIds) - ..insert(int.parse(ctrl.attrs["at"]!), ctrl.id)); - } - } - } -} - -changeProps(Map controls, List> allProps) { - for (var props in allProps) { - final ctrl = controls[props["i"]]; - if (ctrl != null) { - var attrs = Map.of(ctrl.attrs); - for (var propName in props.keys) { - if (propName == "i") { - continue; - } - var v = props[propName]; - if (v == null || v == "") { - attrs.remove(propName); - } else { - attrs[propName] = v; - } - } - controls[ctrl.id] = ctrl.copyWith(attrs: attrs); - } - } -} - -appendProps(Map controls, List> allProps) { - for (var props in allProps) { - final ctrl = controls[props["i"]]; - if (ctrl != null) { - var attrs = Map.of(ctrl.attrs); - for (var propName in props.keys) { - if (propName == "i") { - continue; - } - var v = props[propName] ?? ""; - attrs[propName] = v + (props[propName] ?? ""); - } - controls[ctrl.id] = ctrl.copyWith(attrs: attrs); - } - } -} - -cleanControls(Map controls, List ids) { - for (var id in ids) { - // remove all children - final descendantIds = getAllDescendantIds(controls, id); - for (var descendantId in descendantIds) { - controls.remove(descendantId); - } - - // cleanup children collection - controls[id] = controls[id]!.copyWith(childIds: []); - } -} - -removeControls(Map controls, List ids) { - for (var id in ids) { - final ctrl = controls[id]; - - // remove all children - final descendantIds = getAllDescendantIds(controls, id); - for (var descendantId in descendantIds) { - controls.remove(descendantId); - } - - // delete control itself - if (ctrl != null) { - for (var handler in ctrl.onRemove) { - handler(); - } - } - controls.remove(id); - - // remove control's ID from parent's children collection - final parentCtrl = controls[ctrl!.pid]; - controls[parentCtrl!.id] = parentCtrl.copyWith( - childIds: - parentCtrl.childIds.where((childId) => childId != id).toList()); - } -} - -List getAllDescendantIds(Map controls, String id) { - if (controls[id] != null) { - List childIds = []; - for (String childId in controls[id]!.childIds) { - childIds - ..add(childId) - ..addAll(getAllDescendantIds(controls, childId)); - } - return childIds; - } - return []; -} - -void sendRouteChangeEvent(FletControlBackend backend, String route) { - backend.updateControlState("page", {"route": route}, client: false); - backend.triggerControlEvent("page", "route_change", route); -} diff --git a/packages/flet/lib/src/routing/route_state.dart b/packages/flet/lib/src/routing/route_state.dart index 0fc61a296..869e437fe 100644 --- a/packages/flet/lib/src/routing/route_state.dart +++ b/packages/flet/lib/src/routing/route_state.dart @@ -19,7 +19,6 @@ class RouteState extends ChangeNotifier { if (_route == route) return; _route = route; - debugPrint("Route changed to: $route"); notifyListeners(); } diff --git a/packages/flet/lib/src/services/browser_context_menu.dart b/packages/flet/lib/src/services/browser_context_menu.dart new file mode 100644 index 000000000..3915a94b8 --- /dev/null +++ b/packages/flet/lib/src/services/browser_context_menu.dart @@ -0,0 +1,41 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; + +import '../flet_service.dart'; + +class BrowserContextMenuService extends FletService { + BrowserContextMenuService({required super.control}); + + @override + void init() { + super.init(); + debugPrint( + "BrowserContextMenuService(${control.id}).init: ${control.properties}"); + control.addInvokeMethodListener(_invokeMethod); + } + + @override + void update() { + debugPrint( + "BrowserContextMenuService(${control.id}).update: ${control.properties}"); + } + + Future _invokeMethod(String name, dynamic args) async { + debugPrint("BrowserContextMenuService.$name($args)"); + switch (name) { + case "disable_menu": + return BrowserContextMenu.disableContextMenu(); + case "enable_menu": + return BrowserContextMenu.enableContextMenu(); + default: + throw Exception("Unknown BrowserContextMenu method: $name"); + } + } + + @override + void dispose() { + debugPrint("BrowserContextMenuService(${control.id}).dispose()"); + control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } +} diff --git a/packages/flet/lib/src/services/clipboard.dart b/packages/flet/lib/src/services/clipboard.dart new file mode 100644 index 000000000..ca047a0c1 --- /dev/null +++ b/packages/flet/lib/src/services/clipboard.dart @@ -0,0 +1,40 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; + +import '../flet_service.dart'; + +class ClipboardService extends FletService { + ClipboardService({required super.control}); + + @override + void init() { + super.init(); + debugPrint("ClipboardService(${control.id}).init: ${control.properties}"); + control.addInvokeMethodListener(_invokeMethod); + } + + @override + void update() { + debugPrint("ClipboardService(${control.id}).update: ${control.properties}"); + } + + Future _invokeMethod(String name, dynamic args) async { + debugPrint("ClipboardService.$name($args)"); + switch (name) { + case "set": + Clipboard.setData(ClipboardData(text: args["data"])); + case "get": + var data = await Clipboard.getData(Clipboard.kTextPlain); + return data?.text; + default: + throw Exception("Unknown Clipboard method: $name"); + } + } + + @override + void dispose() { + debugPrint("ClipboardService(${control.id}).dispose()"); + control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } +} diff --git a/packages/flet/lib/src/services/file_picker.dart b/packages/flet/lib/src/services/file_picker.dart new file mode 100644 index 000000000..92d9ed728 --- /dev/null +++ b/packages/flet/lib/src/services/file_picker.dart @@ -0,0 +1,195 @@ +import 'package:collection/collection.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart' as http; + +import '../flet_service.dart'; +import '../utils/file_picker.dart'; +import '../utils/platform.dart'; + +class FilePickerService extends FletService { + FilePickerService({required super.control}); + + List? _files; + + @override + void init() { + super.init(); + debugPrint("FilePickerService(${control.id}).init: ${control.properties}"); + control.addInvokeMethodListener(_invokeMethod); + } + + @override + void dispose() { + debugPrint("FilePickerService(${control.id}).dispose()"); + control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } + + Future _invokeMethod(String name, dynamic args) async { + debugPrint("FilePicker.$name($args)"); + var dialogTitle = args["dialog_title"]; + var fileType = parseFileType(args["file_type"], FileType.any)!; + var initialDirectory = args["initial_directory"]; + var allowedExtensions = (args["allowed_extensions"] as List?) + ?.map((e) => e.toString()) + .toList(); + var srcBytes = args["src_bytes"]; + + if (allowedExtensions != null && allowedExtensions.isNotEmpty) { + fileType = FileType.custom; + } + switch (name) { + case "upload": + var files = args["files"]; + if (files != null && _files != null) { + uploadFiles(files, control.backend.pageUri); + } + case "pick_files": + _files = (await FilePicker.platform.pickFiles( + dialogTitle: dialogTitle, + initialDirectory: initialDirectory, + lockParentWindow: true, + type: fileType, + allowedExtensions: allowedExtensions, + allowMultiple: args["allow_multiple"], + withData: false, + withReadStream: true)) + ?.files; + return _files != null + ? _files!.asMap().entries.map((file) { + return FilePickerFile( + id: file.key, // use entry's index as id + name: file.value.name, + path: kIsWeb ? null : file.value.path, + size: file.value.size) + .toMap(); + }).toList() + : []; + case "save_file": + if (kIsWeb) { + throw Exception("Save File dialog is not supported on web."); + } else if ((isAndroidMobile() || isIOSMobile()) && srcBytes == null) { + throw Exception( + "\"src_bytes\" is required when saving a file on Android or iOS."); + } + return await FilePicker.platform.saveFile( + dialogTitle: dialogTitle, + fileName: args["file_name"] != null || !isIOSMobile() + ? args["file_name"] + : "new-file", + initialDirectory: initialDirectory, + lockParentWindow: true, + type: fileType, + allowedExtensions: allowedExtensions, + bytes: srcBytes); + case "get_directory_path": + if (kIsWeb) { + throw Exception("Get Directory Path dialog is not supported on web."); + } + return await FilePicker.platform.getDirectoryPath( + dialogTitle: dialogTitle, + initialDirectory: initialDirectory, + lockParentWindow: true, + ); + default: + throw Exception("Unknown FilePicker method: $name"); + } + } + + Future uploadFiles(List files, Uri pageUri) async { + var uploadFiles = files.map((u) => FilePickerUploadFile( + id: u["id"], + name: u["name"], + uploadUrl: u["upload_url"], + method: u["method"])); + + for (var uf in uploadFiles) { + var file = ((uf.id != null && uf.id! >= 0 && uf.id! < _files!.length) + ? _files![uf.id!] + : null) // by id + ?? + _files!.firstWhereOrNull((f) => f.name == uf.name); // by name + + if (file != null) { + try { + await uploadFile( + file, getFullUploadUrl(pageUri, uf.uploadUrl), uf.method); + _files!.remove(file); // Remove the uploaded file + } catch (e) { + sendProgress(file.name, null, e.toString()); + } + } else { + debugPrint( + "FilePicker Error: File '${uf.name}' (id: ${uf.id}) not found."); + } + } + } + + Future uploadFile(PlatformFile file, String uploadUrl, String method) async { + final fileReadStream = file.readStream; + if (fileReadStream == null) { + throw Exception('Cannot read file from null stream'); + } + debugPrint("Uploading ${file.name}"); + final streamedRequest = http.StreamedRequest(method, Uri.parse(uploadUrl)) + ..headers.addAll({ + //'Cache-Control': 'no-cache', + }); + streamedRequest.contentLength = file.size; + + // send 0% + sendProgress(file.name, 0, null); + + double lastSent = 0; // send every 10% + double progress = 0; + int bytesSent = 0; + fileReadStream.listen((chunk) async { + //debugPrint(chunk.length); + streamedRequest.sink.add(chunk); + bytesSent += chunk.length; + progress = bytesSent / file.size; + if (progress >= lastSent) { + lastSent += 0.1; + if (progress != 1.0) { + sendProgress(file.name, progress, null); + } + } + }, onDone: () { + streamedRequest.sink.close(); + }); + + var streamedResponse = await streamedRequest.send(); + var response = await http.Response.fromStream(streamedResponse); + if (response.statusCode < 200 || response.statusCode > 204) { + sendProgress(file.name, null, + "Upload endpoint returned code ${response.statusCode}: ${response.body}"); + } else { + // send 100% + sendProgress(file.name, progress, null); + } + } + + void sendProgress(String name, double? progress, String? error) { + control.triggerEvent( + "upload", + FilePickerUploadProgressEvent( + name: name, progress: progress, error: error) + .toMap()); + } + + String getFullUploadUrl(Uri pageUri, String uploadUrl) { + Uri uploadUri = Uri.parse(uploadUrl); + if (!uploadUri.hasAuthority) { + return Uri( + scheme: pageUri.scheme, + host: pageUri.host, + port: pageUri.port, + path: uploadUri.path, + query: uploadUri.query) + .toString(); + } else { + return uploadUrl; + } + } +} diff --git a/packages/flet/lib/src/services/haptic_feedback.dart b/packages/flet/lib/src/services/haptic_feedback.dart new file mode 100644 index 000000000..02ee8217b --- /dev/null +++ b/packages/flet/lib/src/services/haptic_feedback.dart @@ -0,0 +1,52 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; + +import '../flet_service.dart'; + +class HapticFeedbackService extends FletService { + HapticFeedbackService({required super.control}); + + @override + void init() { + super.init(); + debugPrint( + "HapticFeedbackService(${control.id}).init: ${control.properties}"); + control.addInvokeMethodListener(_invokeMethod); + } + + @override + void update() { + debugPrint( + "HapticFeedbackService(${control.id}).update: ${control.properties}"); + } + + @override + void dispose() { + debugPrint("HapticFeedbackService(${control.id}).dispose()"); + control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } + + Future _invokeMethod(String name, dynamic args) async { + debugPrint("HapticFeedbackService.$name($args)"); + switch (name) { + case "heavy_impact": + await HapticFeedback.heavyImpact(); + break; + case "light_impact": + await HapticFeedback.lightImpact(); + break; + case "medium_impact": + await HapticFeedback.mediumImpact(); + break; + case "vibrate": + await HapticFeedback.vibrate(); + break; + case "selection_click": + await HapticFeedback.selectionClick(); + break; + default: + throw Exception("Unknown HapticFeedback method: $name"); + } + } +} diff --git a/packages/flet/lib/src/services/semantics_service.dart b/packages/flet/lib/src/services/semantics_service.dart new file mode 100644 index 000000000..6c521ba2a --- /dev/null +++ b/packages/flet/lib/src/services/semantics_service.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; + +import '../flet_service.dart'; +import '../utils/misc.dart'; + +class SemanticsServiceControl extends FletService { + SemanticsServiceControl({required super.control}); + + @override + void init() { + super.init(); + debugPrint("SemanticsService(${control.id}).init: ${control.properties}"); + control.addInvokeMethodListener(_invokeMethod); + } + + @override + void dispose() { + debugPrint("SemanticsService(${control.id}).dispose()"); + control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } + + Future _invokeMethod(String name, dynamic args) async { + debugPrint("SemanticsService.$name($args)"); + var message = args["message"].toString(); + switch (name) { + case "announce_message": + return SemanticsService.announce( + message, args["rtl"] ? TextDirection.rtl : TextDirection.ltr, + assertiveness: control.getAssertiveness( + args["assertiveness"], Assertiveness.polite)!); + case "announce_tooltip": + return SemanticsService.tooltip(message); + default: + throw Exception("Unknown SemanticsService method: $name"); + } + } +} diff --git a/packages/flet/lib/src/services/service_binding.dart b/packages/flet/lib/src/services/service_binding.dart new file mode 100644 index 000000000..e5527b3a1 --- /dev/null +++ b/packages/flet/lib/src/services/service_binding.dart @@ -0,0 +1,32 @@ +import '../flet_backend.dart'; +import '../flet_service.dart'; +import '../models/control.dart'; + +class ServiceBinding { + final Control control; + final FletBackend backend; + FletService? _service; + + ServiceBinding({required this.control, required this.backend}) { + for (var extension in backend.extensions) { + _service = extension.createService(control); + if (_service != null) { + break; + } + } + if (_service == null) { + throw Exception("Unknown service: ${control.type}"); + } + _service?.init(); + control.addListener(_onControlUpdated); + } + + void dispose() { + control.removeListener(_onControlUpdated); + _service?.dispose(); + } + + void _onControlUpdated() { + _service?.update(); + } +} diff --git a/packages/flet/lib/src/services/service_registry.dart b/packages/flet/lib/src/services/service_registry.dart new file mode 100644 index 000000000..8fe1be2fc --- /dev/null +++ b/packages/flet/lib/src/services/service_registry.dart @@ -0,0 +1,43 @@ +import 'package:flutter/cupertino.dart'; + +import '../flet_backend.dart'; +import '../models/control.dart'; +import 'service_binding.dart'; + +class ServiceRegistry { + final Control control; + final String propertyName; + final FletBackend backend; + final Map _services = {}; + + ServiceRegistry( + {required this.control, + required this.propertyName, + required this.backend}) { + debugPrint("Init service registry: ${control.id}"); + control.addListener(_onServicesUpdated); + _onServicesUpdated(); + } + + void _onServicesUpdated() { + var serviceControls = control.children(propertyName); + debugPrint("_onServicesUpdated(${serviceControls.length})"); + + // newly added services + for (var serviceControl in serviceControls) { + if (!_services.containsKey(serviceControl.id)) { + _services[serviceControl.id] = + ServiceBinding(control: serviceControl, backend: backend); + } + } + + // removed services + for (var serviceId in _services.keys.toList()) { + if (!serviceControls + .any((serviceControl) => serviceControl.id == serviceId)) { + _services[serviceId]!.dispose(); + _services.remove(serviceId); + } + } + } +} diff --git a/packages/flet/lib/src/services/shake_detector.dart b/packages/flet/lib/src/services/shake_detector.dart new file mode 100644 index 000000000..a2d51ba32 --- /dev/null +++ b/packages/flet/lib/src/services/shake_detector.dart @@ -0,0 +1,124 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:sensors_plus/sensors_plus.dart'; + +import '../flet_service.dart'; +import '../utils/numbers.dart'; + +class ShakeDetectorService extends FletService { + ShakeDetectorService({required super.control}); + + StreamSubscription? _subscription; + + int _mShakeTimestamp = DateTime.now().millisecondsSinceEpoch; + int _mShakeCount = 0; + + int _minimumShakeCount = 1; + int _shakeSlopTimeMs = 500; + int _shakeCountResetTimeMs = 3000; + double _shakeThresholdGravity = 2.7; + + @override + void init() { + debugPrint("ShakeDetectorService(${control.id}).init()"); + super.init(); + update(); + } + + @override + void update() { + debugPrint( + "ShakeDetectorService(${control.id}).update: ${control.properties}"); + + var minimumShakeCount = control.getInt("minimum_shake_count", 1)!; + var shakeSlopTimeMs = control.getInt("shake_slop_time_ms", 500)!; + var shakeCountResetTimeMs = + control.getInt("shake_count_reset_time_ms", 3000)!; + var shakeThresholdGravity = + control.getDouble("shake_threshold_gravity", 2.7)!; + + // Update config if changed + if (minimumShakeCount != _minimumShakeCount || + shakeSlopTimeMs != _shakeSlopTimeMs || + shakeCountResetTimeMs != _shakeCountResetTimeMs || + shakeThresholdGravity != _shakeThresholdGravity) { + _minimumShakeCount = minimumShakeCount; + _shakeSlopTimeMs = shakeSlopTimeMs; + _shakeCountResetTimeMs = shakeCountResetTimeMs; + _shakeThresholdGravity = shakeThresholdGravity; + + _stopListening(); + _startListening(); + } + } + + void _startListening() { + _subscription = accelerometerEventStream().listen((event) { + final gX = event.x / 9.80665; + final gY = event.y / 9.80665; + final gZ = event.z / 9.80665; + + final gForce = sqrt(gX * gX + gY * gY + gZ * gZ); + + if (gForce > _shakeThresholdGravity) { + final now = DateTime.now().millisecondsSinceEpoch; + + if (_mShakeTimestamp + _shakeSlopTimeMs > now) { + return; + } + + if (_mShakeTimestamp + _shakeCountResetTimeMs < now) { + _mShakeCount = 0; + } + + _mShakeTimestamp = now; + _mShakeCount++; + + if (_mShakeCount >= _minimumShakeCount) { + control.triggerEvent("shake"); + } + } + }); + } + + void _stopListening() { + _subscription?.cancel(); + _subscription = null; + } + + @override + void dispose() { + debugPrint("ShakeDetectorService(${control.id}).dispose()"); + _stopListening(); + super.dispose(); + } +} + +/* +Source: https://github.com/dieringe/shake/blob/master/lib/shake.dart + +Copyright 2019 Deven Joshi + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. +*/ diff --git a/packages/flet/lib/src/services/shared_preferences.dart b/packages/flet/lib/src/services/shared_preferences.dart new file mode 100644 index 000000000..b9b112ee1 --- /dev/null +++ b/packages/flet/lib/src/services/shared_preferences.dart @@ -0,0 +1,52 @@ +import 'package:flutter/cupertino.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../flet_service.dart'; + +class SharedPreferencesService extends FletService { + SharedPreferencesService({required super.control}); + + @override + void init() { + super.init(); + debugPrint( + "SharedPreferencesService(${control.id}).init: ${control.properties}"); + control.addInvokeMethodListener(_invokeMethod); + } + + @override + void update() { + debugPrint( + "SharedPreferencesService(${control.id}).update: ${control.properties}"); + } + + Future _invokeMethod(String name, dynamic args) async { + var prefs = await SharedPreferences.getInstance(); + switch (name) { + case "set": + return prefs.setString(args["key"]!, args["value"]!); + case "get": + return prefs.getString(args["key"]!); + case "contains_key": + return prefs.containsKey(args["key"]!); + case "get_keys": + return prefs + .getKeys() + .where((key) => key.startsWith(args["key_prefix"]!)) + .toList(); + case "remove": + return prefs.remove(args["key"]!); + case "clear": + return prefs.clear(); + default: + throw Exception("Unknown SharedPreferences method: $name"); + } + } + + @override + void dispose() { + debugPrint("SharedPreferencesService(${control.id}).dispose()"); + control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } +} diff --git a/packages/flet/lib/src/services/storage_paths.dart b/packages/flet/lib/src/services/storage_paths.dart new file mode 100644 index 000000000..9f8a6a130 --- /dev/null +++ b/packages/flet/lib/src/services/storage_paths.dart @@ -0,0 +1,66 @@ +import 'package:flutter/cupertino.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; + +import '../flet_service.dart'; +import '../utils/platform.dart'; + +class StoragePaths extends FletService { + StoragePaths({required super.control}); + + @override + void init() { + super.init(); + debugPrint("StoragePaths(${control.id}).init: ${control.properties}"); + control.addInvokeMethodListener(_invokeMethod); + } + + Future _invokeMethod(String name, dynamic args) async { + debugPrint("StoragePaths.$name($args)"); + // path_provider doesn't support web + if (!isWebPlatform()) { + switch (name) { + case "get_application_cache_directory": + return (await getApplicationCacheDirectory()).path; + case "get_application_documents_directory": + return (await getApplicationDocumentsDirectory()).path; + case "get_application_support_directory": + return (await getApplicationSupportDirectory()).path; + case "get_downloads_directory": + return (await getDownloadsDirectory())?.path; + case "get_external_cache_directories": + return isAndroidMobile() + ? (await getExternalCacheDirectories()) + ?.map((e) => e.path) + .toList() + : null; + case "get_external_storage_directories": + return isAndroidMobile() + ? (await getExternalStorageDirectories()) + ?.map((e) => e.path) + .toList() + : null; + case "get_library_directory": + return isApplePlatform() ? (await getLibraryDirectory()).path : null; + case "get_external_cache_directory": + return isAndroidMobile() + ? (await getExternalStorageDirectory())?.path + : null; + case "get_temporary_directory": + return (await getTemporaryDirectory()).path; + case "get_console_log_filename": + return path.join( + (await getApplicationCacheDirectory()).path, "console.log"); + default: + throw Exception("Unknown StoragePaths method: $name"); + } + } + } + + @override + void dispose() { + debugPrint("StoragePaths(${control.id}).dispose()"); + control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } +} diff --git a/packages/flet/lib/src/services/url_launcher.dart b/packages/flet/lib/src/services/url_launcher.dart new file mode 100644 index 000000000..25f1f2727 --- /dev/null +++ b/packages/flet/lib/src/services/url_launcher.dart @@ -0,0 +1,48 @@ +import 'package:flutter/cupertino.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../flet_service.dart'; +import '../utils/launch_url.dart'; +import '../utils/numbers.dart'; + +class UrlLauncherService extends FletService { + UrlLauncherService({required super.control}); + + @override + void init() { + super.init(); + debugPrint("UrlLauncherService(${control.id}).init: ${control.properties}"); + control.addInvokeMethodListener(_invokeMethod); + } + + @override + void update() { + debugPrint( + "UrlLauncherService(${control.id}).update: ${control.properties}"); + } + + Future _invokeMethod(String name, dynamic args) async { + debugPrint("UrlLauncherService.$name($args)"); + switch (name) { + case "launch_url": + return openWebBrowser(args["url"]!, + webWindowName: args["web_window_name"], + webPopupWindow: parseBool(args["web_popup_window"]), + windowWidth: parseInt(args["window_width"]), + windowHeight: parseInt(args["window_height"])); + case "can_launch_url": + return canLaunchUrl(Uri.parse(args["url"]!)); + case "close_in_app_web_view": + return closeInAppWebView(); + default: + throw Exception("Unknown UrlLauncher method: $name"); + } + } + + @override + void dispose() { + debugPrint("UrlLauncherService(${control.id}).dispose()"); + control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } +} diff --git a/packages/flet/lib/src/transport/flet_backend_channel.dart b/packages/flet/lib/src/transport/flet_backend_channel.dart new file mode 100644 index 000000000..3bb8a9f9d --- /dev/null +++ b/packages/flet/lib/src/transport/flet_backend_channel.dart @@ -0,0 +1,48 @@ +import '../protocol/message.dart'; +import '../utils/platform_utils_web.dart' + if (dart.library.io) "../utils/platform_utils_non_web.dart"; +import 'flet_backend_channel_javascript_web.dart' + if (dart.library.io) "flet_backend_channel_javascript_io.dart"; +import 'flet_backend_channel_mock.dart'; +import 'flet_backend_channel_socket.dart'; +import 'flet_backend_channel_web_socket.dart'; + +typedef FletBackendChannelOnDisconnectCallback = void Function(); +typedef FletBackendChannelOnMessageCallback = void Function(Message message); + +abstract class FletBackendChannel { + factory FletBackendChannel( + {required String address, + required Map args, + required bool forcePyodide, + required FletBackendChannelOnDisconnectCallback onDisconnect, + required FletBackendChannelOnMessageCallback onMessage}) { + if (isFletWebPyodideMode() || forcePyodide) { + // Pyodide/JavaScript + return FletJavaScriptBackendChannel( + address: address, + args: args, + onDisconnect: onDisconnect, + onMessage: onMessage); + } else if (address.startsWith("http://") || + address.startsWith("https://")) { + // WebSocket + return FletWebSocketBackendChannel( + address: address, onDisconnect: onDisconnect, onMessage: onMessage); + } else if (address == "mock") { + // Mock + return FletMockBackendChannel( + address: address, onDisconnect: onDisconnect, onMessage: onMessage); + } else { + // TCP or UDS + return FletSocketBackendChannel( + address: address, onDisconnect: onDisconnect, onMessage: onMessage); + } + } + + Future connect(); + bool get isLocalConnection; + int get defaultReconnectIntervalMs; + void send(Message message); + void disconnect(); +} diff --git a/packages/flet/lib/src/transport/flet_backend_channel_javascript_io.dart b/packages/flet/lib/src/transport/flet_backend_channel_javascript_io.dart new file mode 100644 index 000000000..65149e543 --- /dev/null +++ b/packages/flet/lib/src/transport/flet_backend_channel_javascript_io.dart @@ -0,0 +1,30 @@ +import '../protocol/message.dart'; +import 'flet_backend_channel.dart'; + +class FletJavaScriptBackendChannel implements FletBackendChannel { + final String address; + final Map args; + final FletBackendChannelOnMessageCallback onMessage; + final FletBackendChannelOnDisconnectCallback onDisconnect; + + FletJavaScriptBackendChannel( + {required this.address, + required this.args, + required this.onDisconnect, + required this.onMessage}); + + @override + connect() async {} + + @override + bool get isLocalConnection => true; + + @override + int get defaultReconnectIntervalMs => 10; + + @override + void send(Message data) {} + + @override + void disconnect() {} +} diff --git a/packages/flet/lib/src/transport/flet_backend_channel_javascript_web.dart b/packages/flet/lib/src/transport/flet_backend_channel_javascript_web.dart new file mode 100644 index 000000000..93ea9ed54 --- /dev/null +++ b/packages/flet/lib/src/transport/flet_backend_channel_javascript_web.dart @@ -0,0 +1,59 @@ +import 'dart:js_interop'; + +import 'package:flutter/foundation.dart'; +import 'package:msgpack_dart/msgpack_dart.dart' as msgpack; + +import '../protocol/message.dart'; +import 'flet_backend_channel.dart'; + +@JS() +external JSPromise jsConnect( + String appId, JSAny args, JSExportedDartFunction onMessage); + +@JS() +external void jsSend(String appId, JSUint8Array data); + +@JS() +external void jsDisconnect(String appId); + +typedef FletBackendJavascriptChannelOnMessageCallback = void Function( + List message); + +class FletJavaScriptBackendChannel implements FletBackendChannel { + final String address; + final Map args; + final FletBackendChannelOnMessageCallback onMessage; + final FletBackendChannelOnDisconnectCallback onDisconnect; + + FletJavaScriptBackendChannel( + {required this.address, + required this.args, + required this.onDisconnect, + required this.onMessage}); + + @override + connect() async { + debugPrint("Connecting to Flet JavaScript channel $address..."); + await jsConnect(address, args.jsify()!, _onMessage.toJS).toDart; + } + + void _onMessage(JSUint8Array data) { + onMessage(Message.fromList(msgpack.deserialize(data.toDart))); + } + + @override + bool get isLocalConnection => true; + + @override + int get defaultReconnectIntervalMs => 10000; + + @override + void send(Message message) { + jsSend(address, msgpack.serialize(message.toList()).toJS); + } + + @override + void disconnect() { + jsDisconnect(address); + } +} diff --git a/packages/flet/lib/src/transport/flet_backend_channel_mock.dart b/packages/flet/lib/src/transport/flet_backend_channel_mock.dart new file mode 100644 index 000000000..c07caa61f --- /dev/null +++ b/packages/flet/lib/src/transport/flet_backend_channel_mock.dart @@ -0,0 +1,306 @@ +import 'dart:math'; + +import 'package:flutter/foundation.dart'; + +import '../protocol/message.dart'; +import 'flet_backend_channel.dart'; + +class FletMockBackendChannel implements FletBackendChannel { + FletBackendChannelOnMessageCallback onMessage; + FletBackendChannelOnDisconnectCallback onDisconnect; + + FletMockBackendChannel( + {required String address, + required this.onDisconnect, + required this.onMessage}); + @override + bool get isLocalConnection => true; + + @override + int get defaultReconnectIntervalMs => 500; + + @override + Future connect() async { + await Future.delayed( + const Duration(seconds: 1)); // Simulating async operation + debugPrint("Connected to the Mock Flet backend channel"); + //_scenario_line_chart_simple(); + _scenario_register(); + //_scenario_test_services(); + //_scenario_call_window_methods(); + } + + _scenario_register() async { + onMessage(Message(action: MessageAction.registerClient, payload: { + "id": 1, + "patch": { + "show_semantics_debugger": false, + "theme_mode": "system", + // "platform": "ios", + // "adaptive": true, + "fonts": { + "Kanit": + "https://raw.githubusercontent.com/google/fonts/master/ofl/kanit/Kanit-Bold.ttf", + "Open Sans": "/fonts/OpenSans-Regular.ttf" + }, + "offstage": { + "_c": "Offstage", + "_i": 8, + "controls": [ + {"_c": "Text", "_i": 20, "text": "OFF1"} + ] + }, + "_user_services": {"_c": "ServiceRegistry", "_i": 10, "services": []}, + "_page_services": {"_c": "ServiceRegistry", "_i": 11, "services": []}, + "views": [ + { + "_c": "View", + "_i": 20, + "route": "/", + "controls": [ + {"_c": "Text", "_i": 3, "text": "Hello, world"}, + {"_c": "Text", "_i": 4, "text": "Second line"}, + { + "_c": "Row", + "_i": 5, + "controls": [ + {"_c": "Text", "_i": 6, "text": "1st in Row"}, + {"_c": "Text", "_i": 7, "text": "2nd in Row"} + ] + } + ] + } + ] + } + })); + + await Future.delayed(const Duration(seconds: 3)); + + onMessage(Message(action: MessageAction.patchControl, payload: { + "id": 2, + "patch": {"width": 300, "height": 300} + })); + } + + _scenario_line_chart_simple() async { + onMessage(Message(action: MessageAction.registerClient, payload: { + "id": 1, + "patch": { + "views": [ + { + "_c": "View", + "_i": 20, + "route": "/", + "controls": [ + { + "_c": "LineChart", + "_i": 30, + "line_bars": [ + { + "_c": "LineChartBarData", + "_i": 31, + "spots": [ + {"x": 1, "y": 1}, + {"x": 2, "y": 0.5}, + {"x": 3, "y": 0.6}, + {"x": 4, "y": 0.7}, + {"x": 5, "y": 0.2}, + {"x": 6, "y": 0.4} + ] + }, + { + "_c": "LineChartBarData", + "_i": 32, + "spots": [ + {"x": 1, "y": 0.8}, + {"x": 2, "y": 0.45}, + {"x": 3, "y": 0.55}, + {"x": 4, "y": 0.65}, + {"x": 5, "y": 0.1}, + {"x": 6, "y": 0.3} + ] + } + ] + }, + ] + } + ] + } + })); + + var random = Random(); + + for (int i = 7; i < 100; i++) { + var y = random.nextDouble(); + await Future.delayed(const Duration(milliseconds: 200)); + + onMessage(Message(action: MessageAction.patchControl, payload: { + "id": 30, + "patch": { + "line_bars": { + //"\$d": [1], + "0": { + "spots": { + "\$d": [0], + //"0": {"y": 0.8}, + "5": { + "\$a": {"x": i, "y": y} + } + } + }, + "1": { + "spots": { + "\$d": [0], + //"0": {"y": 0.8}, + "5": { + "\$a": {"x": i, "y": y - 0.1} + } + } + } + } + } + })); + } + + await Future.delayed(const Duration(seconds: 2)); + + onMessage(Message(action: MessageAction.patchControl, payload: { + "id": 30, + "patch": {"a": 1} + })); + } + + _scenario_test_services() async { + onMessage(Message(action: MessageAction.patchControl, payload: { + "id": 11, + "patch": { + "services": { + 0: { + "\$a": {"_c": "Clipboard", "_i": 31, "var1": "a"} + } + } + } + })); + + await Future.delayed(const Duration(seconds: 2)); + + onMessage(Message(action: MessageAction.patchControl, payload: { + "id": 31, + "patch": {"var1": "b", "var2": "c"} + })); + + await Future.delayed(const Duration(seconds: 2)); + + onMessage(Message(action: MessageAction.invokeControlMethod, payload: { + "id": 31, + "name": "get", + "args": {"a": 1, "b": false} + })); + + await Future.delayed(const Duration(seconds: 2)); + + onMessage(Message(action: MessageAction.patchControl, payload: { + "id": 10, + "patch": { + "services": { + "\$d": [0] + } + } + })); + + // onMessage(Message( + // action: MessageAction.invokeControlMethod, + // payload: {"id": 9, "name": "close", "args": {}})); + } + + scenario_call_window_methods() async { + onMessage(Message( + action: MessageAction.invokeControlMethod, + payload: {"id": 9, "name": "to_front", "args": {}})); + + await Future.delayed(const Duration(seconds: 3)); + + onMessage(Message( + action: MessageAction.invokeControlMethod, + payload: {"id": 9, "name": "close", "args": {}})); + } + + scenario_1() async { + onMessage(Message(action: MessageAction.patchControl, payload: { + "id": 1, + "patch": { + "title": "Hello, title!", + //"theme_mode": "light", + "window": {"width": 1024}, + "views": { + "0": { + "controls": { + "0": {"text": "Hello, world!!!"} + } + } + } + } + })); + + await Future.delayed(const Duration(seconds: 3)); + + onMessage(Message(action: MessageAction.patchControl, payload: { + "id": 1, + "patch": { + "fonts": { + "aboreto": + "https://github.com/google/fonts/raw/refs/heads/main/ofl/aboreto/Aboreto-Regular.ttf", + "\$d": ["Open Sans"] + }, + // "offstage": { + // "controls": { + // 1: { + // "\$a": {"_c": "Text", "_i": 21, "text": "OFF2"} + // } + // } + // }, + "views": { + "0": { + "controls": { + "\$d": [0] + } + } + } + } + })); + + await Future.delayed(const Duration(seconds: 3)); + + onMessage(Message(action: MessageAction.patchControl, payload: { + "id": 8, + "patch": { + "controls": { + 1: { + "\$a": {"_c": "Text", "_i": 21, "text": "ON2"} + } + } + } + })); + + await Future.delayed(const Duration(seconds: 3)); + + onMessage(Message(action: MessageAction.patchControl, payload: { + "id": 5, + "patch": { + "controls": { + "\$d": [1] + } + } + })); + } + + @override + void send(Message message) { + debugPrint("Send message: ${message.toList()}"); + } + + @override + void disconnect() { + debugPrint("Disconnected from the Mock Flet backend channel"); + } +} diff --git a/packages/flet/lib/src/transport/flet_backend_channel_socket.dart b/packages/flet/lib/src/transport/flet_backend_channel_socket.dart new file mode 100644 index 000000000..a1fcb79a0 --- /dev/null +++ b/packages/flet/lib/src/transport/flet_backend_channel_socket.dart @@ -0,0 +1,104 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:msgpack_dart/msgpack_dart.dart' as msgpack; + +import '../protocol/message.dart'; +import '../utils/networking.dart'; +import 'flet_backend_channel.dart'; +import 'flet_msgpack_decoder.dart'; +import 'flet_msgpack_encoder.dart'; +import 'streaming_msgpack_deserializer.dart'; + +const int defaultLocalReconnectInterval = 200; +const int defaultPublicReconnectInterval = 500; + +class FletSocketBackendChannel implements FletBackendChannel { + String address; + FletBackendChannelOnMessageCallback onMessage; + FletBackendChannelOnDisconnectCallback onDisconnect; + Socket? _socket; + late final bool _isLocalConnection; + late final int _defaultReconnectIntervalMs; + + // Create an instance of the StreamingDeserializer. + // This object buffers incoming chunks and decodes complete MessagePack objects. + final StreamingMsgpackDeserializer _streamingDeserializer; + + FletSocketBackendChannel({ + required this.address, + required this.onDisconnect, + required this.onMessage, + }) : _streamingDeserializer = + StreamingMsgpackDeserializer(extDecoder: FletMsgpackDecoder()); + + @override + connect() async { + debugPrint("Connecting to Socket $address..."); + + if (address.startsWith("tcp://")) { + var u = Uri.parse(address); + _isLocalConnection = await isPrivateHost(u.host); + _defaultReconnectIntervalMs = _isLocalConnection + ? defaultLocalReconnectInterval + : defaultPublicReconnectInterval; + _socket = await Socket.connect(u.host, u.port); + debugPrint( + 'Connected to: ${_socket!.remoteAddress.address}:${_socket!.remotePort}'); + } else { + final udsPath = InternetAddress(address, type: InternetAddressType.unix); + _isLocalConnection = true; + _defaultReconnectIntervalMs = defaultLocalReconnectInterval; + _socket = await Socket.connect(udsPath, 0); + debugPrint('Connected to: $udsPath'); + } + + // Listen for incoming data. + _socket!.listen( + (Uint8List data) { + debugPrint("Received packet: ${data.length}"); + // Feed the incoming chunk into the streaming deserializer. + _streamingDeserializer.addChunk(data); + // Try to decode complete MessagePack messages from buffered data. + var messages = _streamingDeserializer.decodeMessages(); + for (var message in messages) { + //debugPrint('Decoded message: ${message.toString()}'); + _onMessage(message); + } + }, + onError: (error) { + debugPrint("Error: $error"); + _socket?.destroy(); + onDisconnect(); + }, + onDone: () { + debugPrint('Server disconnected.'); + _socket?.destroy(); + onDisconnect(); + }, + ); + } + + @override + bool get isLocalConnection => _isLocalConnection; + + @override + int get defaultReconnectIntervalMs => _defaultReconnectIntervalMs; + + // Note: At this point, the incoming message is already a decoded MessagePack object. + _onMessage(dynamic message) { + onMessage(Message.fromList(message)); + } + + @override + void send(Message message) { + // Serialize the message using MessagePack and send it. + _socket!.add( + msgpack.serialize(message.toList(), extEncoder: FletMsgpackEncoder())); + } + + @override + void disconnect() { + _socket?.destroy(); + } +} diff --git a/packages/flet/lib/src/flet_server_protocol_web_socket.dart b/packages/flet/lib/src/transport/flet_backend_channel_web_socket.dart similarity index 69% rename from packages/flet/lib/src/flet_server_protocol_web_socket.dart rename to packages/flet/lib/src/transport/flet_backend_channel_web_socket.dart index 8facdc0d7..908ffe03c 100644 --- a/packages/flet/lib/src/flet_server_protocol_web_socket.dart +++ b/packages/flet/lib/src/transport/flet_backend_channel_web_socket.dart @@ -1,20 +1,22 @@ import 'package:flutter/foundation.dart'; +import 'package:msgpack_dart/msgpack_dart.dart' as msgpack; import 'package:web_socket_channel/web_socket_channel.dart'; -import 'flet_server_protocol.dart'; -import 'utils/networking.dart'; -import 'utils/platform_utils_non_web.dart' - if (dart.library.js) "utils/platform_utils_web.dart"; -import 'utils/uri.dart'; +import '../protocol/message.dart'; +import '../utils/networking.dart'; +import '../utils/platform_utils_web.dart' + if (dart.library.io) "../utils/platform_utils_non_web.dart"; +import '../utils/uri.dart'; +import 'flet_backend_channel.dart'; -class FletWebSocketServerProtocol implements FletServerProtocol { +class FletWebSocketBackendChannel implements FletBackendChannel { late final String _wsUrl; late final bool _isLocalConnection; - FletServerProtocolOnMessageCallback onMessage; - FletServerProtocolOnDisconnectCallback onDisconnect; + FletBackendChannelOnMessageCallback onMessage; + FletBackendChannelOnDisconnectCallback onDisconnect; WebSocketChannel? _channel; - FletWebSocketServerProtocol( + FletWebSocketBackendChannel( {required String address, required this.onDisconnect, required this.onMessage}) { @@ -29,7 +31,7 @@ class FletWebSocketServerProtocol implements FletServerProtocol { @override Future connect() async { - debugPrint("Connecting to WebSocket server $_wsUrl..."); + debugPrint("Connecting to WebSocket $_wsUrl..."); try { // todo var uri = Uri.parse(_wsUrl); @@ -53,12 +55,12 @@ class FletWebSocketServerProtocol implements FletServerProtocol { } _onMessage(message) { - onMessage(message); + onMessage(Message.fromList(msgpack.deserialize(message))); } @override - void send(String message) { - _channel?.sink.add(message); + void send(Message message) { + _channel?.sink.add(msgpack.serialize(message.toList())); } @override diff --git a/packages/flet/lib/src/transport/flet_msgpack_decoder.dart b/packages/flet/lib/src/transport/flet_msgpack_decoder.dart new file mode 100644 index 000000000..d363ccafb --- /dev/null +++ b/packages/flet/lib/src/transport/flet_msgpack_decoder.dart @@ -0,0 +1,25 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:msgpack_dart/msgpack_dart.dart'; + +class FletMsgpackDecoder extends ExtDecoder { + final codec = const Utf8Codec(); + + @override + dynamic decodeObject(int extType, Uint8List data) { + if (extType == 1) { + var isoDate = codec.decode(data); + return DateTime.parse(isoDate); + } else if (extType == 2) { + var parts = + codec.decode(data).split(":").map((s) => int.parse(s)).toList(); + return TimeOfDay(hour: parts[0], minute: parts[1]); + } else if (extType == 3) { + var microseconds = int.parse(codec.decode(data)); + return Duration(microseconds: microseconds); + } + return null; + } +} diff --git a/packages/flet/lib/src/transport/flet_msgpack_encoder.dart b/packages/flet/lib/src/transport/flet_msgpack_encoder.dart new file mode 100644 index 000000000..31f276414 --- /dev/null +++ b/packages/flet/lib/src/transport/flet_msgpack_encoder.dart @@ -0,0 +1,33 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:msgpack_dart/msgpack_dart.dart'; + +class FletMsgpackEncoder extends ExtEncoder { + final codec = const Utf8Codec(); + + @override + int extTypeForObject(dynamic object) { + if (object is DateTime) { + return 1; + } else if (object is TimeOfDay) { + return 2; + } else if (object is Duration) { + return 3; + } + return 0; + } + + @override + Uint8List encodeObject(dynamic object) { + if (object is DateTime) { + return codec.encode(object.toIso8601String()); + } else if (object is TimeOfDay) { + return codec.encode("${object.hour}:${object.minute}"); + } else if (object is Duration) { + return codec.encode(object.inMicroseconds.toString()); + } + return Uint8List(0); + } +} diff --git a/packages/flet/lib/src/transport/streaming_msgpack_deserializer.dart b/packages/flet/lib/src/transport/streaming_msgpack_deserializer.dart new file mode 100644 index 000000000..41c8dff14 --- /dev/null +++ b/packages/flet/lib/src/transport/streaming_msgpack_deserializer.dart @@ -0,0 +1,291 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:msgpack_dart/msgpack_dart.dart'; + +/// Thrown when a complete MessagePack object cannot be decoded +/// because the data is incomplete. +class IncompleteDataError extends FormatError { + IncompleteDataError(super.message); +} + +class _Deserializer { + final ExtDecoder? _extDecoder; + final codec = const Utf8Codec(); + final Uint8List _list; + final ByteData _data; + int _offset = 0; + + /// If false, decoded binary data buffers will reference underlying input + /// buffer and thus may change when the content of input buffer changes. + /// If true, decoded buffers are copies and the underlying input buffer is + /// free to change after decoding. + final bool copyBinaryData; + + /// The current offset after decoding + int get offset => _offset; + + _Deserializer( + Uint8List list, { + ExtDecoder? extDecoder, + this.copyBinaryData = false, + int initialOffset = 0, + }) : _list = list, + _data = + ByteData.view(list.buffer, list.offsetInBytes, list.lengthInBytes), + _extDecoder = extDecoder { + _offset = initialOffset; + } + + // Checks that at least [required] bytes are available, + // or throws an IncompleteDataError. + void _ensureAvailable(int required) { + if (_offset + required > _list.length) { + throw IncompleteDataError( + "Not enough data: require $required more bytes, available ${_list.length - _offset}"); + } + } + + dynamic decode() { + _ensureAvailable(1); + final u = _list[_offset++]; + if (u <= 127) { + return u; + } else if ((u & 0xE0) == 0xE0) { + // negative small integer + return u - 256; + } else if ((u & 0xE0) == 0xA0) { + return _readString(u & 0x1F); + } else if ((u & 0xF0) == 0x90) { + return _readArray(u & 0xF); + } else if ((u & 0xF0) == 0x80) { + return _readMap(u & 0xF); + } + switch (u) { + case 0xc0: + return null; + case 0xc2: + return false; + case 0xc3: + return true; + case 0xcc: + return _readUInt8(); + case 0xcd: + return _readUInt16(); + case 0xce: + return _readUInt32(); + case 0xcf: + return _readUInt64(); + case 0xd0: + return _readInt8(); + case 0xd1: + return _readInt16(); + case 0xd2: + return _readInt32(); + case 0xd3: + return _readInt64(); + case 0xca: + return _readFloat(); + case 0xcb: + return _readDouble(); + case 0xd9: + return _readString(_readUInt8()); + case 0xda: + return _readString(_readUInt16()); + case 0xdb: + return _readString(_readUInt32()); + case 0xc4: + return _readBuffer(_readUInt8()); + case 0xc5: + return _readBuffer(_readUInt16()); + case 0xc6: + return _readBuffer(_readUInt32()); + case 0xdc: + return _readArray(_readUInt16()); + case 0xdd: + return _readArray(_readUInt32()); + case 0xde: + return _readMap(_readUInt16()); + case 0xdf: + return _readMap(_readUInt32()); + case 0xd4: + return _readExt(1); + case 0xd5: + return _readExt(2); + case 0xd6: + return _readExt(4); + case 0xd7: + return _readExt(8); + case 0xd8: + return _readExt(16); + case 0xc7: + return _readExt(_readUInt8()); + case 0xc8: + return _readExt(_readUInt16()); + case 0xc9: + return _readExt(_readUInt32()); + default: + throw FormatError("Invalid MessagePack format"); + } + } + + int _readInt8() { + _ensureAvailable(1); + return _data.getInt8(_offset++); + } + + int _readUInt8() { + _ensureAvailable(1); + return _data.getUint8(_offset++); + } + + int _readUInt16() { + _ensureAvailable(2); + final res = _data.getUint16(_offset); + _offset += 2; + return res; + } + + int _readInt16() { + _ensureAvailable(2); + final res = _data.getInt16(_offset); + _offset += 2; + return res; + } + + int _readUInt32() { + _ensureAvailable(4); + final res = _data.getUint32(_offset); + _offset += 4; + return res; + } + + int _readInt32() { + _ensureAvailable(4); + final res = _data.getInt32(_offset); + _offset += 4; + return res; + } + + int _readUInt64() { + _ensureAvailable(8); + final res = _data.getUint64(_offset); + _offset += 8; + return res; + } + + int _readInt64() { + _ensureAvailable(8); + final res = _data.getInt64(_offset); + _offset += 8; + return res; + } + + double _readFloat() { + _ensureAvailable(4); + final res = _data.getFloat32(_offset); + _offset += 4; + return res; + } + + double _readDouble() { + _ensureAvailable(8); + final res = _data.getFloat64(_offset); + _offset += 8; + return res; + } + + Uint8List _readBuffer(int length) { + _ensureAvailable(length); + final res = + Uint8List.view(_list.buffer, _list.offsetInBytes + _offset, length); + _offset += length; + return copyBinaryData ? Uint8List.fromList(res) : res; + } + + String _readString(int length) { + final list = _readBuffer(length); + final len = list.length; + for (int i = 0; i < len; ++i) { + if (list[i] > 127) { + return codec.decode(list); + } + } + return String.fromCharCodes(list); + } + + List _readArray(int length) { + final res = List.filled(length, null, growable: false); + for (int i = 0; i < length; ++i) { + res[i] = decode(); + } + return res; + } + + Map _readMap(int length) { + final res = {}; + while (length > 0) { + res[decode()] = decode(); + --length; + } + return res; + } + + dynamic _readExt(int length) { + final extType = _readUInt8(); + final data = _readBuffer(length); + return _extDecoder?.decodeObject(extType, data); + } +} + +/// A helper to decode MessagePack data as it arrives in chunks. +/// Call [addChunk] for every incoming piece of data, +/// then [decodeMessages] to retrieve complete messages. +class StreamingMsgpackDeserializer { + final ExtDecoder? _extDecoder; + final bool copyBinaryData; + final BytesBuilder _buffer = BytesBuilder(); + + StreamingMsgpackDeserializer( + {ExtDecoder? extDecoder, this.copyBinaryData = false}) + : _extDecoder = extDecoder; + + /// Adds a new chunk of MessagePack data. + void addChunk(Uint8List chunk) { + _buffer.add(chunk); + } + + /// Attempts to decode as many complete messages as possible + /// from the buffered data. Incomplete trailing data remains in the buffer. + List decodeMessages() { + List messages = []; + Uint8List data = _buffer.takeBytes(); + int offset = 0; + while (offset < data.length) { + try { + // Create a Deserializer using the current offset + _Deserializer d = _Deserializer( + data, + extDecoder: _extDecoder, + copyBinaryData: copyBinaryData, + initialOffset: offset, + ); + dynamic message = d.decode(); + messages.add(message); + offset = d.offset; + } on IncompleteDataError { + // Not enough data to decode a full message; break out of the loop. + break; + } on FormatError { + // For actual format errors (not just incomplete data), + // rethrow or handle as needed. + rethrow; + } + } + // If there is any leftover (incomplete) data, put it back into the buffer. + if (offset < data.length) { + _buffer.add(data.sublist(offset)); + } + return messages; + } +} diff --git a/packages/flet/lib/src/utils/alignment.dart b/packages/flet/lib/src/utils/alignment.dart index a5f20920b..4fc112825 100644 --- a/packages/flet/lib/src/utils/alignment.dart +++ b/packages/flet/lib/src/utils/alignment.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -6,70 +5,82 @@ import 'package:flutter/material.dart'; import '../models/control.dart'; import 'numbers.dart'; -MainAxisAlignment? parseMainAxisAlignment(String? alignment, - [MainAxisAlignment? defValue]) { - if (alignment == null) { - return defValue; - } +MainAxisAlignment? parseMainAxisAlignment(String? value, + [MainAxisAlignment? defaultValue]) { + if (value == null) return defaultValue; + return MainAxisAlignment.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == alignment.toLowerCase()) ?? - defValue; + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; } -CrossAxisAlignment? parseCrossAxisAlignment(String? alignment, - [CrossAxisAlignment? defValue]) { - if (alignment == null) { - return defValue; - } +CrossAxisAlignment? parseCrossAxisAlignment(String? value, + [CrossAxisAlignment? defaultValue]) { + if (value == null) return defaultValue; + return CrossAxisAlignment.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == alignment.toLowerCase()) ?? - defValue; + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; } -TabAlignment? parseTabAlignment(String? alignment, [TabAlignment? defValue]) { - if (alignment == null) { - return defValue; - } +TabAlignment? parseTabAlignment(String? value, [TabAlignment? defaultValue]) { + if (value == null) return defaultValue; + return TabAlignment.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == alignment.toLowerCase()) ?? - defValue; + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; } -WrapAlignment? parseWrapAlignment(String? alignment, - [WrapAlignment? defValue]) { - if (alignment == null) { - return defValue; - } +WrapAlignment? parseWrapAlignment(String? value, + [WrapAlignment? defaultValue]) { + if (value == null) return defaultValue; + return WrapAlignment.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == alignment.toLowerCase()) ?? - defValue; + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; } -WrapCrossAlignment? parseWrapCrossAlignment(String? alignment, - [WrapCrossAlignment? defValue]) { - if (alignment == null) { - return defValue; - } +WrapCrossAlignment? parseWrapCrossAlignment(String? value, + [WrapCrossAlignment? defaultValue]) { + if (value == null) return defaultValue; + return WrapCrossAlignment.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == alignment.toLowerCase()) ?? - defValue; + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; } -Alignment? parseAlignment(Control control, String propName, - [Alignment? defValue]) { - var v = control.attrString(propName, null); - if (v == null) { - return defValue; +Alignment? parseAlignment(dynamic value, [Alignment? defaultValue]) { + if (value == null) return defaultValue; + return Alignment(parseDouble(value['x'], 0)!, parseDouble(value['y'], 0)!); +} + +extension AlignmentParsers on Control { + MainAxisAlignment? getMainAxisAlignment(String propertyName, + [MainAxisAlignment? defaultValue]) { + return parseMainAxisAlignment(get(propertyName), defaultValue); } - final j1 = json.decode(v); - return alignmentFromJson(j1, defValue); -} + CrossAxisAlignment? getCrossAxisAlignment(String propertyName, + [CrossAxisAlignment? defaultValue]) { + return parseCrossAxisAlignment(get(propertyName), defaultValue); + } -Alignment? alignmentFromJson(Map? json, - [Alignment? defValue]) { - if (json == null) { - return defValue; + TabAlignment? getTabAlignment(String propertyName, + [TabAlignment? defaultValue]) { + return parseTabAlignment(get(propertyName), defaultValue); } - return Alignment(parseDouble(json['x'], 0)!, parseDouble(json['y'],0)!); -} + + WrapAlignment? getWrapAlignment(String propertyName, + [WrapAlignment? defaultValue]) { + return parseWrapAlignment(get(propertyName), defaultValue); + } + + WrapCrossAlignment? getWrapCrossAlignment(String propertyName, + [WrapCrossAlignment? defaultValue]) { + return parseWrapCrossAlignment(get(propertyName), defaultValue); + } + + Alignment? getAlignment(String propertyName, [Alignment? defaultValue]) { + return parseAlignment(get(propertyName), defaultValue); + } +} \ No newline at end of file diff --git a/packages/flet/lib/src/utils/animations.dart b/packages/flet/lib/src/utils/animations.dart index 5498ef945..398bc32c0 100644 --- a/packages/flet/lib/src/utils/animations.dart +++ b/packages/flet/lib/src/utils/animations.dart @@ -1,33 +1,23 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; import '../models/control.dart'; -import 'numbers.dart'; import 'time.dart'; -ImplicitAnimationDetails? parseAnimation(Control control, String propName, +ImplicitAnimationDetails? parseAnimation(dynamic value, [ImplicitAnimationDetails? defaultValue]) { - var v = control.attrString(propName, null); - if (v == null) { + if (value == null) { return defaultValue; - } - - final j1 = json.decode(v); - return animationFromJSON(j1); -} - -ImplicitAnimationDetails animationFromJSON(dynamic json) { - if (json is int) { - return ImplicitAnimationDetails( - duration: Duration(milliseconds: parseInt(json, 0)!), - curve: Curves.linear); - } else if (json is bool && json == true) { + } else if (value is bool && value == true) { return ImplicitAnimationDetails( duration: const Duration(milliseconds: 1000), curve: Curves.linear); + } else if (value is int) { + return ImplicitAnimationDetails( + duration: parseDuration(value, const Duration())!, + curve: Curves.linear); } - - return ImplicitAnimationDetails.fromJson(json); + return ImplicitAnimationDetails( + duration: parseDuration(value["duration"], const Duration())!, + curve: parseCurve(value["curve"], Curves.linear)!); } class ImplicitAnimationDetails { @@ -35,15 +25,9 @@ class ImplicitAnimationDetails { final Curve curve; ImplicitAnimationDetails({required this.duration, required this.curve}); - - factory ImplicitAnimationDetails.fromJson(Map json) { - return ImplicitAnimationDetails( - duration: Duration(milliseconds: json["duration"] as int), - curve: parseCurve(json["curve"], Curves.linear)!); - } } -Curve? parseCurve(String? value, [Curve? defValue]) { +Curve? parseCurve(String? value, [Curve? defaultValue]) { switch (value?.toLowerCase()) { case "bouncein": return Curves.bounceIn; @@ -128,30 +112,33 @@ Curve? parseCurve(String? value, [Curve? defValue]) { case "slowmiddle": return Curves.slowMiddle; default: - return defValue; + return defaultValue; } } -AnimationStyle? parseAnimationStyle(Control control, String propName, +AnimationStyle? parseAnimationStyle(dynamic value, [AnimationStyle? defaultValue]) { - var v = control.attrString(propName, null); - if (v == null) { - return defaultValue; - } + if (value == null) return defaultValue; - final j1 = json.decode(v); - return animationStyleFromJSON(j1); + return AnimationStyle( + curve: parseCurve(value["curve"]), + reverseCurve: parseCurve(value["reverse_curve"]), + duration: parseDuration(value["duration"]), + reverseDuration: parseDuration(value["reverse_duration"])); } -AnimationStyle animationStyleFromJSON(dynamic json, - [AnimationStyle? defaultValue]) { - if (json == null) { - return defaultValue!; +extension AnimationParsers on Control { + ImplicitAnimationDetails? getAnimation(String propertyName, + [ImplicitAnimationDetails? defaultValue]) { + return parseAnimation(get(propertyName), defaultValue); } - return AnimationStyle( - curve: parseCurve(json["curve"]), - reverseCurve: parseCurve(json["reverse_curve"]), - duration: durationFromJSON(json["duration"]), - reverseDuration: durationFromJSON(json["reverse_duration"])); -} + Curve? getCurve(String propertyName, [Curve? defaultValue]) { + return parseCurve(get(propertyName), defaultValue); + } + + AnimationStyle? getAnimationStyle(String propertyName, + [AnimationStyle? defaultValue]) { + return parseAnimationStyle(get(propertyName), defaultValue); + } +} \ No newline at end of file diff --git a/packages/flet/lib/src/utils/auto_complete.dart b/packages/flet/lib/src/utils/auto_complete.dart index d67831236..a4a3ca556 100644 --- a/packages/flet/lib/src/utils/auto_complete.dart +++ b/packages/flet/lib/src/utils/auto_complete.dart @@ -1,9 +1,5 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; -import '../models/control.dart'; - @immutable class AutoCompleteSuggestion { const AutoCompleteSuggestion({ @@ -23,10 +19,7 @@ class AutoCompleteSuggestion { return key; } - Map toJson() => { - 'key': key, - 'value': value, - }; + Map toMap() => {'key': key, 'value': value}; @override bool operator ==(Object other) { @@ -42,44 +35,33 @@ class AutoCompleteSuggestion { int get hashCode => Object.hash(key, value); } -List parseAutoCompleteSuggestions( - Control control, String propName) { - var v = control.attrString(propName, null); - if (v == null) { - return []; - } - - final j1 = json.decode(v); - return autoCompleteSuggestionsFromJSON(j1); -} +List? parseAutoCompleteSuggestions( + dynamic value, [ + List? defaultValue, +]) { + if (value == null) return defaultValue; -List autoCompleteSuggestionsFromJSON(dynamic json) { List m = []; - if (json is List) { - json.map((e) => autoCompleteSuggestionFromJSON(e)).toList().forEach((e) { - if (e != null) { - m.add(e); + + if (value is List) { + for (var json in value) { + var key = json["key"]; + var val = json["value"]; + + if ((key == null || key.toString().isEmpty) && + (val == null || val.toString().isEmpty)) { + continue; } - }); - } - return m; -} -AutoCompleteSuggestion? autoCompleteSuggestionFromJSON(dynamic json) { - var key = json["key"]; - var value = json["value"]; - if ((key == null || key.toString().isEmpty) && - (value == null || value.toString().isEmpty)) { - return null; - } - if (key == null && value != null) { - key = value; - } - if (value == null && key != null) { - value = key; + key ??= val; + val ??= key; + + m.add(AutoCompleteSuggestion( + key: key.toString(), + value: val.toString(), + )); + } } - return AutoCompleteSuggestion( - key: key.toString(), - value: value.toString(), - ); -} + + return m; +} \ No newline at end of file diff --git a/packages/flet/lib/src/utils/autofill.dart b/packages/flet/lib/src/utils/autofill.dart index 32fe132d7..6d8879580 100644 --- a/packages/flet/lib/src/utils/autofill.dart +++ b/packages/flet/lib/src/utils/autofill.dart @@ -1,35 +1,24 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; import '../models/control.dart'; -List? parseAutofillHints(Control control, String propName) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - - final j1 = json.decode(v); - return autofillHintsFromJson(j1); -} - -List autofillHintsFromJson(dynamic json) { +List? parseAutofillHints(dynamic value, [List? defaultValue]) { + if (value == null) return defaultValue; List hints = []; - if (json is List) { - hints = json - .map((e) => autofillHintFromString(e.toString())) + if (value is List) { + hints = value + .map((e) => parseAutofillHint(e.toString())) .whereType() .toList(); - } else if (json is String) { - hints = [autofillHintFromString(json)].whereType().toList(); + } else if (value is String) { + hints = [parseAutofillHint(value)].whereType().toList(); } return hints; } -String? autofillHintFromString(String? hint, [String? defaultAutoFillHint]) { - switch (hint?.toLowerCase()) { +String? parseAutofillHint(String? value, [String? defaultValue]) { + switch (value?.toLowerCase()) { case 'addresscity': return AutofillHints.addressCity; case 'addresscityandstate': @@ -163,18 +152,34 @@ String? autofillHintFromString(String? hint, [String? defaultAutoFillHint]) { case 'username': return AutofillHints.username; default: - return defaultAutoFillHint; + return defaultValue; } } -AutofillContextAction? parseAutofillContextAction(String? action, - [AutofillContextAction? defaultAction]) { - switch (action?.toLowerCase()) { +AutofillContextAction? parseAutofillContextAction(String? value, + [AutofillContextAction? defaultValue]) { + switch (value?.toLowerCase()) { case 'commit': return AutofillContextAction.commit; case 'cancel': return AutofillContextAction.cancel; default: - return defaultAction; + return defaultValue; + } +} + +extension AutofillParsers on Control { + List? getAutofillHints(String propertyName, + [List? defaultValue]) { + return parseAutofillHints(get(propertyName), defaultValue); + } + + String? getAutofillHint(String propertyName, [String? defaultValue]) { + return parseAutofillHint(get(propertyName), defaultValue); + } + + AutofillContextAction? getAutofillContextAction(String propertyName, + [AutofillContextAction? defaultValue]) { + return parseAutofillContextAction(get(propertyName), defaultValue); } } diff --git a/packages/flet/lib/src/utils/badge.dart b/packages/flet/lib/src/utils/badge.dart index 09cf306b1..d778b6800 100644 --- a/packages/flet/lib/src/utils/badge.dart +++ b/packages/flet/lib/src/utils/badge.dart @@ -1,38 +1,28 @@ -import 'dart:convert'; - import 'package:flet/flet.dart'; import 'package:flutter/material.dart'; -Badge? parseBadge( - Control control, String propName, Widget widget, ThemeData theme) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - final j = json.decode(v); - return badgeFromJSON(j, widget, theme); -} - -Badge? badgeFromJSON(dynamic j, Widget widget, ThemeData theme) { - if (j == null) { - return null; - } else if (j is String) { - return Badge(label: Text(j), child: widget); +extension BadgeParsers on Control { + Widget wrapWithBadge(String propertyName, Widget child, ThemeData theme) { + var badge = get(propertyName); + if (badge == null) { + return child; + } else if (badge is Control) { + badge.notifyParent = true; + return Badge( + label: badge.buildTextOrWidget("label"), + isLabelVisible: badge.getBool("label_visible", true)!, + offset: badge.getOffset("offset"), + alignment: badge.getAlignment("alignment"), + backgroundColor: parseColor(badge.get("bgcolor"), theme), + largeSize: badge.getDouble("large_size"), + padding: badge.getPadding("padding"), + smallSize: badge.getDouble("small_size"), + textColor: parseColor(badge.get("text_color"), theme), + textStyle: badge.getTextStyle("text_style", theme), + child: child, + ); + } else { + return Badge(label: Text(badge.toString()), child: child); + } } - - String? label = j["text"]; - - return Badge( - label: label != null ? Text(label) : null, - isLabelVisible: parseBool(j["label_visible"]) ?? true, - offset: offsetFromJson(j["offset"]), - alignment: alignmentFromJson(j["alignment"]), - backgroundColor: parseColor(theme, j["bgcolor"]), - largeSize: parseDouble(j["large_size"]), - padding: edgeInsetsFromJson(j["padding"]), - smallSize: parseDouble(j["small_size"]), - textColor: parseColor(theme, j["text_color"]), - textStyle: textStyleFromJson(theme, j["text_style"]), - child: widget, - ); } diff --git a/packages/flet/lib/src/utils/borders.dart b/packages/flet/lib/src/utils/borders.dart index 992e9b699..6058e5ec6 100644 --- a/packages/flet/lib/src/utils/borders.dart +++ b/packages/flet/lib/src/utils/borders.dart @@ -1,6 +1,6 @@ import 'dart:collection'; -import 'dart:convert'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import '../models/control.dart'; @@ -8,156 +8,136 @@ import 'colors.dart'; import 'material_state.dart'; import 'numbers.dart'; -BorderRadius? parseBorderRadius(Control control, String propName, - [BorderRadius? defaultValue]) { - var v = control.attrString(propName, null); - if (v == null) { - return defaultValue; - } - - final j1 = json.decode(v); - return borderRadiusFromJSON(j1); -} +BorderRadius? parseBorderRadius(dynamic value, [BorderRadius? defaultValue]) { + if (value == null) return defaultValue; -Radius? parseRadius(Control control, String propName, [Radius? defaultValue]) { - var r = control.attrDouble(propName, null); - if (r == null) { - return defaultValue; + if (value is int || value is double) { + return BorderRadius.all(parseRadius(value)!); } - - return Radius.circular(r); + return BorderRadius.only( + topLeft: parseRadius(value['top_left'], Radius.zero)!, + topRight: parseRadius(value['top_right'], Radius.zero)!, + bottomLeft: parseRadius(value['bottom_left'], Radius.zero)!, + bottomRight: parseRadius(value['bottom_right'], Radius.zero)!, + ); } -Border? parseBorder(ThemeData theme, Control control, String propName, - [Color? defaultSideColor]) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } +Radius? parseRadius(dynamic value, [Radius? defaultValue]) { + var radius = parseDouble(value); + if (radius == null) return defaultValue; - final j1 = json.decode(v); - return borderFromJSON(theme, j1, defaultSideColor); + return Radius.circular(radius); } -BorderSide? parseBorderSide(ThemeData theme, Control control, String propName, - {Color? defaultSideColor}) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - - final j1 = json.decode(v); - return borderSideFromJSON(theme, j1, defaultSideColor); +BorderStyle? parseBorderStyle(String? value, [BorderStyle? defaultValue]) { + if (value == null) return defaultValue; + return BorderStyle.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; } -OutlinedBorder? parseOutlinedBorder(Control control, String propName) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - - final j1 = json.decode(v); - return outlinedBorderFromJSON(j1); +Border? parseBorder(dynamic value, ThemeData? theme, + {Color defaultSideColor = Colors.black, + BorderSide? defaultBorderSide, + Border? defaultValue}) { + if (value == null) return defaultValue; + return Border( + top: parseBorderSide(value['top'], theme, + defaultSideColor: defaultSideColor, + defaultValue: defaultBorderSide ?? BorderSide.none)!, + right: parseBorderSide(value['right'], theme, + defaultSideColor: defaultSideColor, + defaultValue: defaultBorderSide ?? BorderSide.none)!, + bottom: parseBorderSide(value['bottom'], theme, + defaultSideColor: defaultSideColor, + defaultValue: defaultBorderSide ?? BorderSide.none)!, + left: parseBorderSide(value['left'], theme, + defaultSideColor: defaultSideColor, + defaultValue: defaultBorderSide ?? BorderSide.none)!); } -BorderRadius? borderRadiusFromJSON(dynamic json, [BorderRadius? defaultValue]) { - if (json == null) { - return defaultValue; - } - if (json is int || json is double) { - return BorderRadius.all(Radius.circular(parseDouble(json, 0)!)); - } - return BorderRadius.only( - topLeft: Radius.circular(parseDouble(json['tl'], 0)!), - topRight: Radius.circular(parseDouble(json['tr'], 0)!), - bottomLeft: Radius.circular(parseDouble(json['bl'], 0)!), - bottomRight: Radius.circular(parseDouble(json['br'], 0)!), +BorderSide? parseBorderSide(dynamic value, ThemeData? theme, + {Color defaultSideColor = Colors.black, BorderSide? defaultValue}) { + if (value == null) return defaultValue; + return BorderSide( + color: parseColor(value['color'], theme, defaultSideColor)!, + width: parseDouble(value['width'], 1)!, + strokeAlign: + parseDouble(value['stroke_align'], BorderSide.strokeAlignInside)!, + style: parseBorderStyle(value['style'], BorderStyle.solid)!, ); } -Border? borderFromJSON(ThemeData? theme, Map? json, - [Color? defaultSideColor, Border? defaultBorder]) { - if (json == null) { - return defaultBorder; +OutlinedBorder? parseOutlinedBorder(dynamic value, ThemeData? theme, + {BorderSide defaultBorderSide = BorderSide.none, + BorderRadius defaultBorderRadius = BorderRadius.zero, + OutlinedBorder? defaultValue}) { + if (value == null) return defaultValue; + + var borderSide = + parseBorderSide(value["side"], theme, defaultValue: defaultBorderSide)!; + var borderRadius = parseBorderRadius(value["radius"], defaultBorderRadius)!; + + var type = value["_type"]; + switch (type.toLowerCase()) { + case "roundedrectangle": + return RoundedRectangleBorder( + side: borderSide, borderRadius: borderRadius); + case "stadium": + return StadiumBorder(side: borderSide); + case "circle": + return CircleBorder( + side: borderSide, + eccentricity: parseDouble(value["eccentricity"], 0.0)!); + case "beveledrectangle": + return BeveledRectangleBorder( + side: borderSide, borderRadius: borderRadius); + case "continuousrectangle": + return ContinuousRectangleBorder( + side: borderSide, borderRadius: borderRadius); + default: + return defaultValue; } - return Border( - top: borderSideFromJSON(theme, json['t'], defaultSideColor) ?? - BorderSide.none, - right: borderSideFromJSON(theme, json['r'], defaultSideColor) ?? - BorderSide.none, - bottom: borderSideFromJSON(theme, json['b'], defaultSideColor) ?? - BorderSide.none, - left: borderSideFromJSON(theme, json['l'], defaultSideColor) ?? - BorderSide.none); } -BorderSide? borderSideFromJSON(ThemeData? theme, dynamic json, - [Color? defaultSideColor]) { - return json != null - ? BorderSide( - color: - parseColor(theme, json['c'], defaultSideColor ?? Colors.black)!, - width: parseDouble(json['w'], 1)!, - strokeAlign: parseDouble(json['sa'], BorderSide.strokeAlignInside)!, - style: BorderStyle.solid) - : null; -} - -OutlinedBorder? outlinedBorderFromJSON(Map? json) { - if (json == null) { - return null; - } - - String type = json["type"]; - if (type == "roundedRectangle") { - return RoundedRectangleBorder( - borderRadius: borderRadiusFromJSON(json["radius"], BorderRadius.zero)!); - } else if (type == "stadium") { - return const StadiumBorder(); - } else if (type == "circle") { - return const CircleBorder(); - } else if (type == "beveledRectangle") { - return BeveledRectangleBorder( - borderRadius: borderRadiusFromJSON(json["radius"], BorderRadius.zero)!); - } else if (type == "continuousRectangle") { - return ContinuousRectangleBorder( - borderRadius: borderRadiusFromJSON(json["radius"], BorderRadius.zero)!); - } - return null; +OutlinedBorder? parseShape(dynamic value, ThemeData? theme, + {BorderSide defaultBorderSide = BorderSide.none, + BorderRadius defaultBorderRadius = BorderRadius.zero, + OutlinedBorder? defaultValue}) { + return parseOutlinedBorder(value, theme, + defaultBorderSide: defaultBorderSide, + defaultBorderRadius: defaultBorderRadius, + defaultValue: defaultValue); } WidgetStateBorderSide? parseWidgetStateBorderSide( - ThemeData theme, Control control, String propName) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - - var j = json.decode(v); - if (j is Map && (j.containsKey("w") || j.containsKey("c"))) { - j = {"default": j}; + dynamic value, ThemeData theme, + {BorderSide? defaultBorderSide = BorderSide.none, + WidgetStateBorderSide? defaultValue}) { + if (value == null) return defaultValue; + if (value is Map && + (value.containsKey("width") || value.containsKey("color"))) { + value = {"default": value}; } return WidgetStateBorderSideFromJSON( - j, (jv) => borderSideFromJSON(theme, jv), BorderSide.none); + value, (jv) => parseBorderSide(jv, theme), defaultBorderSide); } class WidgetStateBorderSideFromJSON extends WidgetStateBorderSide { - late final Map _states; - late final BorderSide _defaultValue; + late final Map _states; + late final BorderSide? _defaultValue; WidgetStateBorderSideFromJSON( - Map? jsonDictValue, + Map? jsonDictValue, BorderSide? Function(dynamic) converterFromJson, - BorderSide defaultValue) { + BorderSide? defaultValue) { _defaultValue = defaultValue; // preserve user-defined order _states = LinkedHashMap.from( jsonDictValue?.map((k, v) { var key = k.trim().toLowerCase(); - // "" is deprecated and renamed to "default" - if (key == "") key = "default"; return MapEntry(key, converterFromJson(v)); }) ?? {}, @@ -180,12 +160,80 @@ class WidgetStateBorderSideFromJSON extends WidgetStateBorderSide { } WidgetStateProperty? parseWidgetStateOutlinedBorder( - Control control, String propName) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } + dynamic value, ThemeData? theme, + {OutlinedBorder? defaultOutlinedBorder, + WidgetStateProperty? defaultValue}) { + if (value == null) return defaultValue; return getWidgetStateProperty( - jsonDecode(v), (jv) => outlinedBorderFromJSON(jv), null); + value, (jv) => parseOutlinedBorder(jv, theme), defaultOutlinedBorder); +} + +extension BorderParsers on Control { + BorderRadius? getBorderRadius(String propertyName, + [BorderRadius? defaultValue]) { + return parseBorderRadius(get(propertyName), defaultValue); + } + + Radius? getRadius(String propertyName, [Radius? defaultValue]) { + return parseRadius(get(propertyName), defaultValue); + } + + BorderStyle? getBorderStyle(String propertyName, + [BorderStyle? defaultValue]) { + return parseBorderStyle(get(propertyName), defaultValue); + } + + Border? getBorder(String propertyName, ThemeData theme, + {Color defaultSideColor = Colors.black, + BorderSide? defaultBorderSide, + Border? defaultValue}) { + return parseBorder(get(propertyName), theme, + defaultSideColor: defaultSideColor, + defaultBorderSide: defaultBorderSide, + defaultValue: defaultValue); + } + + BorderSide? getBorderSide(String propertyName, ThemeData theme, + {Color defaultSideColor = Colors.black, BorderSide? defaultValue}) { + return parseBorderSide(get(propertyName), theme, + defaultSideColor: defaultSideColor, defaultValue: defaultValue); + } + + OutlinedBorder? getOutlinedBorder(String propertyName, ThemeData? theme, + {BorderSide defaultBorderSide = BorderSide.none, + BorderRadius defaultBorderRadius = BorderRadius.zero, + OutlinedBorder? defaultValue}) { + return parseOutlinedBorder(get(propertyName), theme, + defaultBorderSide: defaultBorderSide, + defaultBorderRadius: defaultBorderRadius, + defaultValue: defaultValue); + } + + OutlinedBorder? getShape(String propertyName, ThemeData? theme, + {BorderSide defaultBorderSide = BorderSide.none, + BorderRadius defaultBorderRadius = BorderRadius.zero, + OutlinedBorder? defaultValue}) { + return parseOutlinedBorder(get(propertyName), theme, + defaultBorderSide: defaultBorderSide, + defaultBorderRadius: defaultBorderRadius, + defaultValue: defaultValue); + } + + WidgetStateBorderSide? getWidgetStateBorderSide( + String propertyName, ThemeData theme, + {BorderSide defaultBorderSide = BorderSide.none, + WidgetStateBorderSide? defaultValue}) { + return parseWidgetStateBorderSide(get(propertyName), theme, + defaultBorderSide: defaultBorderSide, defaultValue: defaultValue); + } + + WidgetStateProperty? getWidgetStateOutlinedBorder( + String propertyName, ThemeData? theme, + {OutlinedBorder? defaultOutlinedBorder, + WidgetStateProperty? defaultValue}) { + return parseWidgetStateOutlinedBorder(get(propertyName), theme, + defaultOutlinedBorder: defaultOutlinedBorder, + defaultValue: defaultValue); + } } diff --git a/packages/flet/lib/src/utils/box.dart b/packages/flet/lib/src/utils/box.dart index bb358d7a8..f632bc645 100644 --- a/packages/flet/lib/src/utils/box.dart +++ b/packages/flet/lib/src/utils/box.dart @@ -5,111 +5,77 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import '../controls/error.dart'; +import '../flet_backend.dart'; import '../models/control.dart'; -import '../models/page_args_model.dart'; +import '../widgets/error.dart'; import 'alignment.dart'; import 'borders.dart'; import 'collections.dart'; import 'colors.dart'; import 'gradient.dart'; import 'images.dart'; +import 'misc.dart'; import 'numbers.dart'; -import 'others.dart'; import 'transforms.dart'; -BoxConstraints? parseBoxConstraints(Control control, String propName) { - var v = control.attrString(propName); - if (v == null) { - return null; - } - - final j1 = json.decode(v); - return boxConstraintsFromJSON(j1); -} +BoxConstraints? parseBoxConstraints(dynamic value, + [BoxConstraints? defaultValue]) { + if (value == null) return defaultValue; -BoxConstraints? boxConstraintsFromJSON(dynamic json, - [BoxConstraints? defValue]) { - if (json == null) { - return null; - } return BoxConstraints( - minHeight: parseDouble(json["min_height"], 0.0)!, - minWidth: parseDouble(json["min_width"], 0.0)!, - maxHeight: parseDouble(json["max_height"], double.infinity)!, - maxWidth: parseDouble(json["max_width"], double.infinity)!, + minHeight: parseDouble(value["min_height"], 0.0)!, + minWidth: parseDouble(value["min_width"], 0.0)!, + maxHeight: parseDouble(value["max_height"], double.infinity)!, + maxWidth: parseDouble(value["max_width"], double.infinity)!, ); } -List? parseBoxShadow( - ThemeData theme, Control control, String propName, - [List? defValue]) { - var v = control.attrString(propName); - if (v == null) { - return defValue; - } - - final j1 = json.decode(v); - return boxShadowsFromJSON(theme, j1); -} - -List? boxShadowsFromJSON(ThemeData theme, dynamic json, - [List? defValue]) { - if (json == null) { - return defValue; - } - if (json is List) { - return json.map((e) => boxShadowFromJSON(theme, e)).toList(); +List? parseBoxShadows(dynamic value, ThemeData theme, + [List? defaultValue]) { + if (value == null) return defaultValue; + if (value is List) { + return value.map((e) => parseBoxShadow(e, theme)!).toList(); } else { - return [boxShadowFromJSON(theme, json)]; + return [parseBoxShadow(value, theme)!]; } } -BoxShadow boxShadowFromJSON(ThemeData theme, dynamic json) { - var offset = - json["offset"] != null ? offsetDetailsFromJSON(json["offset"]) : null; +BoxShadow? parseBoxShadow(dynamic value, ThemeData theme, + [BoxShadow? defaultValue]) { + if (value == null) return defaultValue; + + var offset = parseOffset(value["offset"]); return BoxShadow( - color: parseColor(theme, json["color"], const Color(0xFF000000))!, - offset: offset != null ? Offset(offset.x, offset.y) : Offset.zero, - blurStyle: json["blur_style"] != null + color: parseColor(value["color"], theme, const Color(0xFF000000))!, + offset: offset != null ? Offset(offset.dx, offset.dy) : Offset.zero, + blurStyle: value["blur_style"] != null ? BlurStyle.values - .firstWhere((e) => e.name.toLowerCase() == json["blur_style"]) + .firstWhere((e) => e.name.toLowerCase() == value["blur_style"]) : BlurStyle.normal, - blurRadius: parseDouble(json["blur_radius"], 0)!, - spreadRadius: parseDouble(json["spread_radius"], 0)!); + blurRadius: parseDouble(value["blur_radius"], 0)!, + spreadRadius: parseDouble(value["spread_radius"], 0)!); } -BoxDecoration? parseBoxDecoration(ThemeData theme, Control control, - String propName, PageArgsModel? pageArgs) { - var v = control.attrString(propName); - if (v == null) { - return null; - } +BoxDecoration? parseBoxDecoration(dynamic value, BuildContext context, + [BoxDecoration? defaultValue]) { + if (value == null) return defaultValue; + var theme = Theme.of(context); - final j1 = json.decode(v); - return boxDecorationFromJSON(theme, j1, pageArgs); -} - -BoxDecoration? boxDecorationFromJSON( - ThemeData theme, dynamic json, PageArgsModel? pageArgs) { - if (json == null) { - return null; - } - var shape = parseBoxShape(json["shape"], BoxShape.rectangle)!; - var borderRadius = borderRadiusFromJSON(json["border_radius"]); - var color = parseColor(theme, json["color"]); - var gradient = gradientFromJSON(theme, json["gradient"]); - var blendMode = parseBlendMode(json["blend_mode"]); + var shape = parseBoxShape(value["shape"], BoxShape.rectangle)!; + var borderRadius = parseBorderRadius(value["border_radius"]); + var color = parseColor(value["color"], theme); + var gradient = parseGradient(value["gradient"], theme); + var blendMode = parseBlendMode(value["blend_mode"]); return BoxDecoration( color: color, - border: borderFromJSON(theme, json["border"]), + border: parseBorder(value["border"], theme), shape: shape, borderRadius: shape == BoxShape.circle ? null : borderRadius, backgroundBlendMode: color != null || gradient != null ? blendMode : null, - boxShadow: boxShadowsFromJSON(theme, json["shadow"]), + boxShadow: parseBoxShadows(value["shadow"], theme), gradient: gradient, - image: decorationImageFromJSON(theme, json["image"], pageArgs), + image: parseDecorationImage(value["image"], context), ); } @@ -123,16 +89,14 @@ BoxDecoration? boxDecorationFromDetails({ Gradient? gradient, DecorationImage? image, }) { - bool hasCustomProperties = color != null || + // If no custom properties are provided, return null + if (!(color != null || border != null || borderRadius != null || gradient != null || shape != null || boxShadow != null || - image != null; - - // If no custom properties are provided, return null - if (!hasCustomProperties) { + image != null)) { return null; } @@ -148,46 +112,35 @@ BoxDecoration? boxDecorationFromDetails({ ); } -DecorationImage? parseDecorationImage(ThemeData theme, Control control, - String propName, PageArgsModel? pageArgs) { - var v = control.attrString(propName); - if (v == null) { - return null; - } - - final j1 = json.decode(v); - return decorationImageFromJSON(theme, j1, pageArgs); -} +DecorationImage? parseDecorationImage(dynamic value, BuildContext context, + [DecorationImage? defaultValue]) { + if (value == null) return defaultValue; -DecorationImage? decorationImageFromJSON( - ThemeData theme, dynamic json, PageArgsModel? pageArgs) { - if (json == null) { - return null; - } - var src = json["src"]; - var srcBase64 = json["src_base64"]; - ImageProvider? image = getImageProvider(src, srcBase64, pageArgs); + var src = value["src"]; + var srcBase64 = value["src_base64"]; + var srcBytes = value["src_bytes"]; + ImageProvider? image = getImageProvider(context, src, srcBase64, srcBytes); if (image == null) { - return null; + return defaultValue; } return DecorationImage( image: image, - colorFilter: colorFilterFromJSON(json["color_filter"], theme), - fit: parseBoxFit(json["fit"]), - alignment: alignmentFromJson(json["alignment"], Alignment.center)!, - repeat: parseImageRepeat(json["repeat"], ImageRepeat.noRepeat)!, - matchTextDirection: parseBool(json["match_text_direction"], false)!, - scale: parseDouble(json["scale"], 1.0)!, - opacity: parseDouble(json["opacity"], 1.0)!, + colorFilter: parseColorFilter(value["color_filter"], Theme.of(context)), + fit: parseBoxFit(value["fit"]), + alignment: parseAlignment(value["alignment"], Alignment.center)!, + repeat: parseImageRepeat(value["repeat"], ImageRepeat.noRepeat)!, + matchTextDirection: parseBool(value["match_text_direction"], false)!, + scale: parseDouble(value["scale"], 1.0)!, + opacity: parseDouble(value["opacity"], 1.0)!, filterQuality: - parseFilterQuality(json["filter_quality"], FilterQuality.medium)!, - invertColors: parseBool(json["invert_colors"], false)!, - isAntiAlias: parseBool(json["anti_alias"], false)!, + parseFilterQuality(value["filter_quality"], FilterQuality.medium)!, + invertColors: parseBool(value["invert_colors"], false)!, + isAntiAlias: parseBool(value["anti_alias"], false)!, ); } ImageProvider? getImageProvider( - String? src, String? srcBase64, PageArgsModel? pageArgs) { + BuildContext context, String? src, String? srcBase64, Uint8List? srcBytes) { src = src?.trim(); srcBase64 = srcBase64?.trim(); @@ -196,14 +149,17 @@ ImageProvider? getImageProvider( Uint8List bytes = base64Decode(srcBase64); return MemoryImage(bytes); } catch (ex) { - debugPrint("getImageProvider failed decoding srcBase64"); + debugPrint("getImageProvider failed decoding src_base64"); + } + } else if (srcBytes != null && srcBytes.isNotEmpty) { + try { + return MemoryImage(srcBytes); + } catch (ex) { + debugPrint("getImageProvider failed decoding src_bytes"); } } if (src != null && src != "") { - if (pageArgs == null) { - return null; - } - var assetSrc = getAssetSrc(src, pageArgs.pageUri!, pageArgs.assetsDir); + var assetSrc = FletBackend.of(context).getAssetSource(src); return assetSrc.isFile ? getFileImageProvider(assetSrc.path) @@ -218,6 +174,7 @@ Widget buildImage({ required Widget? errorCtrl, required String? src, required String? srcBase64, + required Uint8List srcBytes, double? width, double? height, ImageRepeat repeat = ImageRepeat.noRepeat, @@ -232,14 +189,16 @@ Widget buildImage({ bool excludeFromSemantics = false, FilterQuality filterQuality = FilterQuality.low, bool disabled = false, - required PageArgsModel pageArgs, }) { Widget? image; const String svgTag = " xmlns=\"http://www.w3.org/2000/svg\""; - if (srcBase64 != null && srcBase64.isNotEmpty) { + Uint8List bytes = srcBytes; + if (bytes.isEmpty && srcBase64 != null && srcBase64.isNotEmpty) { + bytes = base64Decode(srcBase64); + } + if (bytes.isNotEmpty) { try { - Uint8List bytes = base64Decode(srcBase64); if (arrayIndexOf(bytes, Uint8List.fromList(utf8.encode(svgTag))) != -1) { image = SvgPicture.memory(bytes, width: width, @@ -279,7 +238,7 @@ Widget buildImage({ : null, semanticsLabel: semanticsLabel); } else { - var assetSrc = getAssetSrc(src, pageArgs.pageUri!, pageArgs.assetsDir); + var assetSrc = FletBackend.of(context).getAssetSource(src); if (assetSrc.isFile) { // from File @@ -354,3 +313,25 @@ Widget buildImage({ } return const ErrorControl("A valid src or src_base64 must be specified."); } + +extension BoxParsers on Control { + BoxConstraints? getBoxConstraints(String propertyName, + [BoxConstraints? defaultValue]) { + return parseBoxConstraints(get(propertyName), defaultValue); + } + + List? getBoxShadows(String propertyName, ThemeData theme, + [List? defaultValue]) { + return parseBoxShadows(get(propertyName), theme, defaultValue); + } + + BoxDecoration? getBoxDecoration(String propertyName, BuildContext context, + [BoxDecoration? defaultValue]) { + return parseBoxDecoration(get(propertyName), context, defaultValue); + } + + DecorationImage? getDecorationImage(String propertyName, BuildContext context, + [DecorationImage? defaultValue]) { + return parseDecorationImage(get(propertyName), context, defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/browser_context_menu.dart b/packages/flet/lib/src/utils/browser_context_menu.dart deleted file mode 100644 index 13cf0982d..000000000 --- a/packages/flet/lib/src/utils/browser_context_menu.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:flutter/services.dart'; - -Future disableBrowserContextMenu() async { - return BrowserContextMenu.disableContextMenu(); -} - -Future enableBrowserContextMenu() async { - return BrowserContextMenu.enableContextMenu(); -} diff --git a/packages/flet/lib/src/utils/buttons.dart b/packages/flet/lib/src/utils/buttons.dart index 9a2449bab..0661e08a1 100644 --- a/packages/flet/lib/src/utils/buttons.dart +++ b/packages/flet/lib/src/utils/buttons.dart @@ -1,9 +1,12 @@ -import 'dart:convert'; - -import 'package:flet/src/utils/transforms.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import '../models/control.dart'; +import '../utils/text.dart'; +import '../utils/theme.dart'; +import '../utils/time.dart'; +import '../utils/transforms.dart'; import 'alignment.dart'; import 'borders.dart'; import 'colors.dart'; @@ -11,10 +14,8 @@ import 'edge_insets.dart'; import 'material_state.dart'; import 'mouse.dart'; import 'numbers.dart'; -import 'text.dart'; -import 'theme.dart'; -ButtonStyle? parseButtonStyle(ThemeData theme, Control control, String propName, +ButtonStyle? parseButtonStyle(dynamic value, ThemeData theme, {Color? defaultForegroundColor, Color? defaultBackgroundColor, Color? defaultOverlayColor, @@ -23,80 +24,47 @@ ButtonStyle? parseButtonStyle(ThemeData theme, Control control, String propName, double? defaultElevation, EdgeInsets? defaultPadding, BorderSide? defaultBorderSide, - OutlinedBorder? defaultShape}) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - - final j1 = json.decode(v); - return buttonStyleFromJSON( - theme, - j1, - defaultForegroundColor, - defaultBackgroundColor, - defaultOverlayColor, - defaultShadowColor, - defaultSurfaceTintColor, - defaultElevation, - defaultPadding, - defaultBorderSide, - defaultShape); -} - -ButtonStyle? buttonStyleFromJSON(ThemeData theme, Map? json, - [Color? defaultForegroundColor, - Color? defaultBackgroundColor, - Color? defaultOverlayColor, - Color? defaultShadowColor, - Color? defaultSurfaceTintColor, - double? defaultElevation, - EdgeInsets? defaultPadding, - BorderSide? defaultBorderSide, - OutlinedBorder? defaultShape]) { - if (json == null) { - return null; - } + OutlinedBorder? defaultShape, + TextStyle? defaultTextStyle, + ButtonStyle? defaultValue}) { + if (value == null) return defaultValue; return ButtonStyle( - foregroundColor: getWidgetStateProperty(json["color"], - (jv) => parseColor(theme, jv as String), defaultForegroundColor), - backgroundColor: getWidgetStateProperty(json["bgcolor"], - (jv) => parseColor(theme, jv as String), defaultBackgroundColor), - overlayColor: getWidgetStateProperty(json["overlay_color"], - (jv) => parseColor(theme, jv as String), defaultOverlayColor), - shadowColor: getWidgetStateProperty(json["shadow_color"], - (jv) => parseColor(theme, jv as String), defaultShadowColor), - surfaceTintColor: getWidgetStateProperty(json["surface_tint_color"], - (jv) => parseColor(theme, jv as String), defaultSurfaceTintColor), - elevation: getWidgetStateProperty( - json["elevation"], (jv) => parseDouble(jv, 0)!, defaultElevation), - animationDuration: json["animation_duration"] != null - ? Duration(milliseconds: parseInt(json["animation_duration"], 0)!) - : null, - padding: getWidgetStateProperty( - json["padding"], (jv) => edgeInsetsFromJson(jv), defaultPadding), + foregroundColor: parseWidgetStateColor(value["color"], theme, + defaultColor: defaultForegroundColor), + backgroundColor: parseWidgetStateColor(value["bgcolor"], theme, + defaultColor: defaultBackgroundColor), + overlayColor: parseWidgetStateColor(value["overlay_color"], theme, + defaultColor: defaultOverlayColor), + shadowColor: parseWidgetStateColor(value["shadow_color"], theme, + defaultColor: defaultShadowColor), + surfaceTintColor: parseWidgetStateColor(value["surface_tint_color"], theme, + defaultColor: defaultSurfaceTintColor), + elevation: parseWidgetStateDouble(value["elevation"], + defaultDouble: defaultElevation), + animationDuration: parseDuration(value["animation_duration"]), + padding: parseWidgetStatePadding(value["padding"], + defaultPadding: defaultPadding), side: getWidgetStateProperty( - json["side"], - (jv) => borderSideFromJSON(theme, jv, theme.colorScheme.outline), + value["side"], + (jv) => parseBorderSide(jv, theme, + defaultSideColor: theme.colorScheme.outline), defaultBorderSide), - shape: getWidgetStateProperty( - json["shape"], (jv) => outlinedBorderFromJSON(jv), defaultShape), - iconColor: getWidgetStateProperty(json["icon_color"], - (jv) => parseColor(theme, jv as String), defaultForegroundColor), - alignment: alignmentFromJson(json["alignment"]), - enableFeedback: parseBool(json["enable_feedback"]), - textStyle: getWidgetStateProperty( - json["text_style"], (jv) => textStyleFromJson(theme, jv)), - iconSize: getWidgetStateProperty( - json["icon_size"], (jv) => parseDouble(jv)), - visualDensity: parseVisualDensity(json["visual_density"]), - mouseCursor: getWidgetStateProperty( - json["mouse_cursor"], (jv) => parseMouseCursor(jv)), + shape: parseWidgetStateOutlinedBorder(value["shape"], theme, + defaultOutlinedBorder: defaultShape), + iconColor: parseWidgetStateColor(value["icon_color"], theme, + defaultColor: defaultForegroundColor), + alignment: parseAlignment(value["alignment"]), + enableFeedback: parseBool(value["enable_feedback"]), + textStyle: parseWidgetStateTextStyle(value["text_style"], theme, + defaultTextStyle: defaultTextStyle), + iconSize: parseWidgetStateDouble(value["icon_size"]), + visualDensity: parseVisualDensity(value["visual_density"]), + mouseCursor: parseWidgetStateMouseCursor(value["mouse_cursor"]), ); } -FloatingActionButtonLocation? parseFloatingActionButtonLocation( - Control control, String propName, [FloatingActionButtonLocation? defValue]) { +FloatingActionButtonLocation? parseFloatingActionButtonLocation(dynamic value, + [FloatingActionButtonLocation? defaultValue]) { const Map fabLocations = { "centerdocked": FloatingActionButtonLocation.centerDocked, "centerfloat": FloatingActionButtonLocation.centerFloat, @@ -119,16 +87,15 @@ FloatingActionButtonLocation? parseFloatingActionButtonLocation( }; try { - var fabLocationOffset = parseOffset(control, propName); - if (fabLocationOffset != null) { - return CustomFloatingActionButtonLocation( - dx: fabLocationOffset.dx, dy: fabLocationOffset.dy); - } else { - return defValue; - } + var fabLocationOffset = parseOffset(value); + + return fabLocationOffset != null + ? CustomFloatingActionButtonLocation( + dx: fabLocationOffset.dx, dy: fabLocationOffset.dy) + : defaultValue; } catch (e) { - var key = control.attrString(propName, "")!.toLowerCase(); - return fabLocations.containsKey(key) ? fabLocations[key]! : defValue; + var key = value.toLowerCase(); + return fabLocations.containsKey(key) ? fabLocations[key]! : defaultValue; } } @@ -156,3 +123,48 @@ class CustomFloatingActionButtonLocation extends FloatingActionButtonLocation { @override String toString() => 'CustomFloatingActionButtonLocation(dx: $dx, dy: $dy)'; } + +CupertinoButtonSize? parseCupertinoButtonSize(String? value, + [CupertinoButtonSize? defaultValue]) { + if (value == null) return defaultValue; + return CupertinoButtonSize.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +extension ButtonParsers on Control { + ButtonStyle? getButtonStyle(String propertyName, ThemeData theme, + {Color? defaultForegroundColor, + Color? defaultBackgroundColor, + Color? defaultOverlayColor, + Color? defaultShadowColor, + Color? defaultSurfaceTintColor, + double? defaultElevation, + EdgeInsets? defaultPadding, + BorderSide? defaultBorderSide, + OutlinedBorder? defaultShape, + ButtonStyle? defaultValue}) { + return parseButtonStyle(get(propertyName), theme, + defaultForegroundColor: defaultForegroundColor, + defaultBackgroundColor: defaultBackgroundColor, + defaultOverlayColor: defaultOverlayColor, + defaultShadowColor: defaultShadowColor, + defaultSurfaceTintColor: defaultSurfaceTintColor, + defaultElevation: defaultElevation, + defaultPadding: defaultPadding, + defaultBorderSide: defaultBorderSide, + defaultShape: defaultShape, + defaultValue: defaultValue); + } + + FloatingActionButtonLocation? getFloatingActionButtonLocation( + String propertyName, + [FloatingActionButtonLocation? defaultValue]) { + return parseFloatingActionButtonLocation(get(propertyName), defaultValue); + } + + CupertinoButtonSize? getCupertinoButtonSize(String propertyName, + [CupertinoButtonSize? defaultValue]) { + return parseCupertinoButtonSize(get(propertyName), defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/charts.dart b/packages/flet/lib/src/utils/charts.dart deleted file mode 100644 index 48145ecbe..000000000 --- a/packages/flet/lib/src/utils/charts.dart +++ /dev/null @@ -1,224 +0,0 @@ -import 'dart:convert'; - -import 'package:collection/collection.dart'; -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/material.dart'; - -import '../models/control.dart'; -import '../utils/gradient.dart'; -import 'colors.dart'; -import 'numbers.dart'; - -FlGridData parseChartGridData(ThemeData theme, Control control, - String horizPropName, String vertPropName) { - var hv = control.attrString(horizPropName, null); - var vv = control.attrString(vertPropName, null); - if (hv == null && vv == null) { - return const FlGridData(show: false); - } - - var hj = hv != null ? json.decode(hv) : null; - var vj = vv != null ? json.decode(vv) : null; - var hLine = flineFromJSON(theme, hj); - var vLine = flineFromJSON(theme, vj); - - return FlGridData( - show: true, - drawHorizontalLine: hv != null, - horizontalInterval: hj != null && hj["interval"] != null - ? parseDouble(hj["interval"]) - : null, - getDrawingHorizontalLine: - hLine == null ? defaultGridLine : (value) => hLine, - drawVerticalLine: vv != null, - verticalInterval: vj != null && vj["interval"] != null - ? parseDouble(vj["interval"]) - : null, - getDrawingVerticalLine: vLine == null ? defaultGridLine : (value) => vLine, - ); -} - -FlLine? parseFlLine(ThemeData theme, Control control, String propName) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - - final j = json.decode(v); - return flineFromJSON(theme, j); -} - -FlLine? parseSelectedFlLine(ThemeData theme, Control control, String propName, - Color? color, Gradient? gradient) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - - final j = json.decode(v); - if (j == false) { - return getInvisibleLine(); - } else if (j == true) { - return FlLine( - color: defaultGetPointColor(color, gradient, 0), strokeWidth: 3); - } - return FlLine( - color: j['color'] != null - ? parseColor(theme, j['color'] as String, Colors.black)! - : defaultGetPointColor(color, gradient, 0), - strokeWidth: parseDouble(j['width'], 2)!, - dashArray: j['dash_pattern'] != null - ? (j['dash_pattern'] as List) - .map((e) => parseInt(e)) - .whereNotNull() - .toList() - : null); -} - -FlLine? flineFromJSON(theme, j) { - if (j == null || - (j['color'] == null && j['width'] == null && j['dash_pattern'] == null)) { - return null; - } - return FlLine( - color: j['color'] != null - ? parseColor(theme, j['color'] as String) ?? Colors.black - : Colors.black, - strokeWidth: parseDouble(j['width'], 2)!, - dashArray: j['dash_pattern'] != null - ? (j['dash_pattern'] as List) - .map((e) => parseInt(e)) - .whereNotNull() - .toList() - : null); -} - -FlDotPainter? parseChartDotPainter( - ThemeData theme, - Control control, - String propName, - Color? barColor, - Gradient? barGradient, - double percentage) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - - final j = json.decode(v); - if (j == false) { - return getInvisiblePainter(); - } else if (j == true) { - return getDefaultPainter(barColor, barGradient, percentage); - } - return chartDotPainterFromJSON(theme, j, barColor, barGradient, percentage); -} - -FlDotPainter? parseChartSelectedDotPainter( - ThemeData theme, - Control control, - String propName, - Color? barColor, - Gradient? barGradient, - double percentage) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - - final j = json.decode(v); - if (j == false) { - return getInvisiblePainter(); - } else if (j == true) { - return getDefaultSelectedPainter(barColor, barGradient, percentage); - } - return chartDotPainterFromJSON(theme, j, barColor, barGradient, percentage); -} - -FlDotPainter? chartDotPainterFromJSON( - ThemeData theme, - Map json, - Color? barColor, - Gradient? barGradient, - double percentage) { - String type = json["type"]; - if (type == "circle") { - return FlDotCirclePainter( - color: json['color'] != null - ? parseColor(theme, json['color'] as String) ?? Colors.green - : defaultGetPointColor(barColor, barGradient, percentage), - radius: parseDouble(json["radius"]), - strokeColor: json['stroke_color'] != null - ? parseColor(theme, json['color'] as String) ?? - const Color.fromRGBO(76, 175, 80, 1) - : defaultGetDotStrokeColor(barColor, barGradient, percentage), - strokeWidth: parseDouble(json["stroke_width"], 1.0)!); - } else if (type == "square") { - return FlDotSquarePainter( - color: json['color'] != null - ? parseColor(theme, json['color'] as String) ?? Colors.green - : defaultGetPointColor(barColor, barGradient, percentage), - size: parseDouble(json["size"], 4.0)!, - strokeColor: json['stroke_color'] != null - ? parseColor(theme, json['color'] as String) ?? - const Color.fromRGBO(76, 175, 80, 1) - : defaultGetDotStrokeColor(barColor, barGradient, percentage), - strokeWidth: parseDouble(json["stroke_width"], 1.0)!); - } else if (type == "cross") { - return FlDotCrossPainter( - color: json['color'] != null - ? parseColor(theme, json['color'] as String) ?? Colors.green - : defaultGetDotStrokeColor(barColor, barGradient, percentage), - size: parseDouble(json["size"], 8.0)!, - width: parseDouble(json["width"], 2.0)!, - ); - } - return null; -} - -FlDotPainter getInvisiblePainter() { - return FlDotCirclePainter(radius: 0, strokeWidth: 0); -} - -FlLine getInvisibleLine() { - return const FlLine(strokeWidth: 0); -} - -FlDotPainter getDefaultPainter( - Color? barColor, Gradient? barGradient, double percentage) { - return FlDotCirclePainter( - radius: 4, - color: defaultGetPointColor(barColor, barGradient, percentage), - strokeColor: defaultGetDotStrokeColor(barColor, barGradient, percentage), - strokeWidth: 1); -} - -FlDotPainter getDefaultSelectedPainter( - Color? barColor, Gradient? barGradient, double percentage) { - return FlDotCirclePainter( - radius: 8, - color: defaultGetPointColor(barColor, barGradient, percentage), - strokeColor: defaultGetDotStrokeColor(barColor, barGradient, percentage), - strokeWidth: 2); -} - -Color defaultGetPointColor( - Color? barColor, Gradient? barGradient, double percentage) { - if (barGradient != null && barGradient is LinearGradient) { - return lerpGradient( - barGradient.colors, barGradient.getSafeColorStops(), percentage / 100); - } - return barGradient?.colors.first ?? barColor ?? Colors.blueGrey; -} - -Color defaultGetDotStrokeColor( - Color? barColor, Gradient? barGradient, double percentage) { - Color color; - if (barGradient != null && barGradient is LinearGradient) { - color = lerpGradient( - barGradient.colors, barGradient.getSafeColorStops(), percentage / 100); - } else { - color = barGradient?.colors.first ?? barColor ?? Colors.blueGrey; - } - return color.darken(); -} diff --git a/packages/flet/lib/src/utils/client_storage.dart b/packages/flet/lib/src/utils/client_storage.dart deleted file mode 100644 index d66fddc4d..000000000 --- a/packages/flet/lib/src/utils/client_storage.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'dart:convert'; - -import 'package:shared_preferences/shared_preferences.dart'; - -import '../flet_control_backend.dart'; -import '../protocol/invoke_method_result.dart'; - -void invokeClientStorage(String methodId, String methodName, - Map args, FletControlBackend backend) async { - sendResult(Object? result, String? error) { - backend.triggerControlEvent( - "page", - "invoke_method_result", - json.encode(InvokeMethodResult( - methodId: methodId, - result: result != null ? json.encode(result) : null, - error: error))); - } - - var prefs = await SharedPreferences.getInstance(); - switch (methodName) { - case "set": - var result = await prefs.setString(args["key"]!, args["value"]!); - sendResult(result, null); - break; - case "get": - sendResult(prefs.getString(args["key"]!), null); - break; - case "containskey": - sendResult(prefs.containsKey(args["key"]!), null); - break; - case "getkeys": - sendResult( - prefs - .getKeys() - .where((key) => key.startsWith(args["key_prefix"]!)) - .toList(), - null); - break; - case "remove": - sendResult(await prefs.remove(args["key"]!), null); - break; - case "clear": - sendResult(await prefs.clear(), null); - break; - } -} diff --git a/packages/flet/lib/src/utils/clipboard.dart b/packages/flet/lib/src/utils/clipboard.dart deleted file mode 100644 index 160d27ac6..000000000 --- a/packages/flet/lib/src/utils/clipboard.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter/services.dart'; - -void setClipboard(String data) async { - await Clipboard.setData(ClipboardData(text: data)); -} - -Future getClipboard() async { - var data = await Clipboard.getData(Clipboard.kTextPlain); - return data?.text; -} diff --git a/packages/flet/lib/src/utils/colors.dart b/packages/flet/lib/src/utils/colors.dart index 5083f33e1..c0512ec1b 100644 --- a/packages/flet/lib/src/utils/colors.dart +++ b/packages/flet/lib/src/utils/colors.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; import '../models/control.dart'; @@ -239,18 +237,29 @@ extension ColorExtension on Color { } } -WidgetStateProperty? parseWidgetStateColor(ThemeData theme, - Control control, String propName, - [Color? defaultValue]) { - var v = control.attrString(propName); - if (v == null) { - return null; - } +WidgetStateProperty? parseWidgetStateColor(dynamic value, + ThemeData theme, + {Color? defaultColor, WidgetStateProperty? defaultValue}) { + if (value == null) return defaultValue; - return getWidgetStateProperty(jsonDecode(v), - (jv) => HexColor.fromString(theme, jv as String), defaultValue); + return getWidgetStateProperty( + value, (jv) => HexColor.fromString(theme, jv as String), defaultColor); } -Color? parseColor(ThemeData? theme, String? colorString, - [Color? defaultColor]) => - HexColor.fromString(theme, colorString, defaultColor); +Color? parseColor(String? value, ThemeData? theme, [Color? defaultColor]) => + HexColor.fromString(theme, value, defaultColor); + +extension ColorParsers on Control { + Color? getColor(String propertyName, BuildContext? context, + [Color? defaultValue]) { + return parseColor(getString(propertyName), + context != null ? Theme.of(context) : null, defaultValue); + } + + WidgetStateProperty? getWidgetStateColor( + String propertyName, ThemeData theme, + {Color? defaultColor, WidgetStateProperty? defaultValue}) { + return parseWidgetStateColor(get(propertyName), theme, + defaultColor: defaultColor, defaultValue: defaultValue); + } +} \ No newline at end of file diff --git a/packages/flet/lib/src/utils/debouncer.dart b/packages/flet/lib/src/utils/debouncer.dart index 759726997..59d893c1a 100644 --- a/packages/flet/lib/src/utils/debouncer.dart +++ b/packages/flet/lib/src/utils/debouncer.dart @@ -1,6 +1,7 @@ -import 'package:flutter/widgets.dart'; import 'dart:async'; +import 'package:flutter/widgets.dart'; + class Debouncer { final int milliseconds; Timer? _timer; diff --git a/packages/flet/lib/src/utils/desktop.dart b/packages/flet/lib/src/utils/desktop.dart index 6dfefcb01..d42f62249 100644 --- a/packages/flet/lib/src/utils/desktop.dart +++ b/packages/flet/lib/src/utils/desktop.dart @@ -1,10 +1,10 @@ -import 'package:flet/src/utils/platform.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:window_manager/window_manager.dart'; import 'package:window_to_front/window_to_front.dart'; -import '../models/window_media_data.dart'; +import '../models/window_state.dart'; +import 'platform.dart'; Future setWindowTitle(String title) async { if (isDesktopPlatform()) { @@ -105,7 +105,7 @@ Future setWindowAlwaysOnTop(bool alwaysOnTop) async { } Future setWindowAlwaysOnBottom(bool alwaysOnBottom) async { - if (isDesktopPlatform()) { + if (isLinuxDesktop() || isWindowsDesktop()) { debugPrint("setWindowAlwaysOnBottom($alwaysOnBottom)"); await windowManager.setAlwaysOnBottom(alwaysOnBottom); } @@ -178,6 +178,18 @@ Future setWindowAlignment(Alignment alignment, [bool animate = true]) async { } } +Future setWindowAspectRatio(double value) async { + if (isDesktopPlatform()) { + await windowManager.setAspectRatio(value); + } +} + +Future setWindowBrightness(Brightness value) async { + if (isDesktopPlatform()) { + await windowManager.setBrightness(value); + } +} + Future minimizeWindow() async { if (isDesktopPlatform() && !await windowManager.isMinimized()) { debugPrint("minimizeWindow()"); @@ -235,12 +247,18 @@ Future windowToFront() async { } } -Future windowStartDragging() async { +Future startDraggingWindow() async { if (isDesktopPlatform()) { await windowManager.startDragging(); } } +Future startResizingWindow(ResizeEdge edge) async { + if (isWindowsDesktop() || isLinuxDesktop()) { + await windowManager.startResizing(edge); + } +} + Future blurWindow() async { if (isDesktopPlatform() && (defaultTargetPlatform == TargetPlatform.windows || @@ -296,22 +314,31 @@ Future setIgnoreMouseEvents(bool ignore) async { } } -Future getWindowMediaData() async { - var m = WindowMediaData(); +Future getWindowState() async { if (isDesktopPlatform()) { - m.isMaximized = await windowManager.isMaximized(); - m.isMinimized = await windowManager.isMinimized(); - m.isFocused = await isFocused(); - m.isFullScreen = await windowManager.isFullScreen(); - m.isVisible = await windowManager.isVisible(); - var size = await windowManager.getSize(); - m.width = size.width; - m.height = size.height; - var pos = await windowManager.getPosition(); - m.left = pos.dx; - m.top = pos.dy; - return m; + final size = await windowManager.getSize(); + final pos = await windowManager.getPosition(); + + return WindowState( + maximized: await windowManager.isMaximized(), + minimized: await windowManager.isMinimized(), + fullScreen: await windowManager.isFullScreen(), + alwaysOnTop: await windowManager.isAlwaysOnTop(), + focused: await isFocused(), + visible: await windowManager.isVisible(), + opacity: await windowManager.getOpacity(), + minimizable: await windowManager.isMinimizable(), + maximizable: await windowManager.isMaximizable(), + resizable: await windowManager.isResizable(), + preventClose: await windowManager.isPreventClose(), + skipTaskBar: await windowManager.isSkipTaskbar(), + width: size.width.toDouble(), + height: size.height.toDouble(), + top: pos.dy.toDouble(), + left: pos.dx.toDouble(), + ); } else { - return Future.value(m); + throw Exception( + "getWindowState() can be called on desktop platforms only."); } -} \ No newline at end of file +} diff --git a/packages/flet/lib/src/utils/dismissible.dart b/packages/flet/lib/src/utils/dismissible.dart index ecc5f70b2..5878d6262 100644 --- a/packages/flet/lib/src/utils/dismissible.dart +++ b/packages/flet/lib/src/utils/dismissible.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -7,57 +5,37 @@ import '../models/control.dart'; import '../utils/numbers.dart'; DismissDirection? parseDismissDirection(String? value, - [DismissDirection? defValue]) { - if (value == null) { - return defValue; - } + [DismissDirection? defaultValue]) { + if (value == null) return defaultValue; return DismissDirection.values.firstWhereOrNull( (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; + defaultValue; } -Map? parseDismissThresholds( - Control control, String propName) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - - final j1 = json.decode(v); - return getDismissThresholds(j1, (jv) => parseDouble(jv, 0)!); -} +Map? parseDismissThresholds(dynamic value, + [Map? defaultValue]) { + if (value == null) return defaultValue; -Map? getDismissThresholds( - dynamic jsonDictValue, T Function(dynamic) converterFromJson) { - if (jsonDictValue == null) { - return null; - } - var j = jsonDictValue; - if (j is! Map) { - j = {"": j}; - } + Map result = {}; + value.forEach((d, t) { + var direction = parseDismissDirection(d, DismissDirection.none)!; + if (direction != DismissDirection.none) { + var threshold = parseDouble(t, 0.0)!; + result[direction] = threshold; + } + }); - return getDismissThresholdsFromJSON(j, converterFromJson); + return result; } -Map getDismissThresholdsFromJSON( - Map? jsonDictValue, Function(dynamic) converterFromJson) { - Map dismissDirectionMap = {}; - - if (jsonDictValue != null) { - jsonDictValue.forEach((directionStr, jv) { - directionStr - .split(",") - .map((s) => s.trim().toLowerCase()) - .forEach((state) { - DismissDirection d = - parseDismissDirection(state, DismissDirection.none)!; - if (d != DismissDirection.none) { - dismissDirectionMap[d] = converterFromJson(jv); - } - }); - }); +extension DismissibleParsers on Control { + DismissDirection? getDismissDirection(String propertyName, + [DismissDirection? defaultValue]) { + return parseDismissDirection(get(propertyName), defaultValue); } - return dismissDirectionMap; -} \ No newline at end of file + Map? getDismissThresholds(String propertyName, + [Map? defaultValue]) { + return parseDismissThresholds(get(propertyName), defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/drawing.dart b/packages/flet/lib/src/utils/drawing.dart index 9b26ccd1d..1d57a634e 100644 --- a/packages/flet/lib/src/utils/drawing.dart +++ b/packages/flet/lib/src/utils/drawing.dart @@ -1,114 +1,102 @@ -import 'dart:convert'; import 'dart:ui' as ui; import 'package:collection/collection.dart'; -import 'package:flet/src/utils/others.dart'; import 'package:flutter/material.dart'; import '../models/control.dart'; import '../utils/numbers.dart'; +import '../utils/transforms.dart'; import 'colors.dart'; import 'gradient.dart'; import 'images.dart'; +import 'misc.dart'; -Paint parsePaint(ThemeData theme, Control control, String propName) { - var v = control.attrString(propName, null); - if (v == null) { - return Paint(); - } - - final j1 = json.decode(v); +Paint? parsePaint(dynamic value, ThemeData theme, [Paint? defaultValue]) { + if (value == null) return defaultValue; - return paintFromJSON(theme, j1); + var paint = Paint(); + paint.color = parseColor(value["color"] as String?, theme, Colors.black)!; + paint.blendMode = parseBlendMode(value["blend_mode"], BlendMode.srcOver)!; + paint.isAntiAlias = parseBool(value["anti_alias"], true)!; + paint.imageFilter = parseBlur(value["blur_image"]); + paint.shader = parsePaintGradient(value["gradient"], theme); + paint.strokeMiterLimit = parseDouble(value["stroke_miter_limit"], 4)!; + paint.strokeWidth = parseDouble(value["stroke_width"], 0)!; + paint.strokeCap = parseStrokeCap(value["stroke_cap"], StrokeCap.butt)!; + paint.strokeJoin = parseStrokeJoin(value["stroke_join"], StrokeJoin.miter)!; + paint.style = parsePaintingStyle(value["style"], PaintingStyle.fill)!; + return paint; } -PaintingStyle? parsePaintingStyle(String? value, [PaintingStyle? defValue]) { - if (value == null) { - return defValue; - } +PaintingStyle? parsePaintingStyle(String? value, + [PaintingStyle? defaultValue]) { + if (value == null) return defaultValue; return PaintingStyle.values.firstWhereOrNull( (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; + defaultValue; } -List? parsePaintStrokeDashPattern(Control control, String propName) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - - final j1 = json.decode(v); +List? parsePaintStrokeDashPattern(dynamic value, + [List? defaultValue]) { + if (value == null) return defaultValue; - return j1["stroke_dash_pattern"] != null - ? (j1["stroke_dash_pattern"] as List) + return value["stroke_dash_pattern"] != null + ? (value["stroke_dash_pattern"] as List) .map((e) => parseDouble(e)) - .whereNotNull() + .nonNulls .toList() : null; } -Paint paintFromJSON(ThemeData? theme, Map json) { - var paint = Paint(); - if (json["color"] != null) { - paint.color = parseColor(theme, json["color"] as String, Colors.black)!; - } - paint.blendMode = parseBlendMode(json["blend_mode"], BlendMode.srcOver)!; - paint.isAntiAlias = parseBool(json["anti_alias"], true)!; - paint.imageFilter = blurImageFilterFromJSON(json["blur_image"]); - paint.shader = paintGradientFromJSON(theme, json["gradient"]); - paint.strokeMiterLimit = parseDouble(json["stroke_miter_limit"], 4)!; - paint.strokeWidth = parseDouble(json["stroke_width"], 0)!; - paint.strokeCap = parseStrokeCap(json["stroke_cap"], StrokeCap.butt)!; - paint.strokeJoin = parseStrokeJoin(json["stroke_join"], StrokeJoin.miter)!; - paint.style = parsePaintingStyle(json["style"], PaintingStyle.fill)!; - return paint; -} +ui.Gradient? parsePaintGradient(Map? value, ThemeData? theme, + [ui.Gradient? defaultValue]) { + if (value == null) return defaultValue; -ui.Gradient? paintGradientFromJSON( - ThemeData? theme, Map? json) { - if (json == null) { - return null; - } - String type = json["type"]; + var type = value["_type"]; + var colorStops = parseGradientStops(value["color_stops"]); + var colors = parseColors(value["colors"], theme); + var tileMode = parseTileMode(value["tile_mode"], TileMode.clamp)!; if (type == "linear") { - return ui.Gradient.linear( - offsetFromJson(json["begin"])!, - offsetFromJson(json["end"])!, - parseColors(theme, json["colors"]), - parseStops(json["color_stops"]), - parseTileMode(json["tile_mode"], TileMode.clamp)!); + return ui.Gradient.linear(parseOffset(value["begin"])!, + parseOffset(value["end"])!, colors, colorStops, tileMode); } else if (type == "radial") { return ui.Gradient.radial( - offsetFromJson(json["center"])!, - parseDouble(json["radius"], 0)!, - parseColors(theme, json["colors"]), - parseStops(json["color_stops"]), - parseTileMode(json["tile_mode"], TileMode.clamp)!, + parseOffset(value["center"])!, + parseDouble(value["radius"], 0)!, + colors, + colorStops, + tileMode, null, - offsetFromJson(json["focal"]), - parseDouble(json["focal_radius"], 0)!, + parseOffset(value["focal"]), + parseDouble(value["focal_radius"], 0)!, ); } else if (type == "sweep") { - Offset center = offsetFromJson(json["center"])!; + Offset center = parseOffset(value["center"])!; return ui.Gradient.sweep( center, - parseColors(theme, json["colors"]), - parseStops(json["color_stops"]), - parseTileMode(json["tile_mode"], TileMode.clamp)!, - parseDouble(json["start_angle"], 0)!, - parseDouble(json["end_angle"], 0)!, + colors, + colorStops, + tileMode, + parseDouble(value["start_angle"], 0)!, + parseDouble(value["end_angle"], 0)!, parseRotationToMatrix4( - json["rotation"], Rect.fromCircle(center: center, radius: 10))); + value["rotation"], Rect.fromCircle(center: center, radius: 10))); } - return null; + return defaultValue; } -Offset? offsetFromJson(dynamic json) { - if (json == null) { - return null; - } else if (json is List && json.length > 1) { - return Offset(parseDouble(json[0], 0)!, parseDouble(json[1], 0)!); - } else { - return Offset(parseDouble(json["x"], 0)!, parseDouble(json["y"], 0)!); +extension DrawingParsers on Control { + Paint? getPaint(String propertyName, ThemeData theme, [Paint? defaultValue]) { + return parsePaint(get(propertyName), theme, defaultValue); + } + + PaintingStyle? getPaintingStyle(String propertyName, + [PaintingStyle? defaultValue]) { + return parsePaintingStyle(get(propertyName), defaultValue); + } + + List? getPaintStrokeDashPattern(String propertyName, + [List? defaultValue]) { + return parsePaintStrokeDashPattern(get(propertyName), defaultValue); } } diff --git a/packages/flet/lib/src/utils/edge_insets.dart b/packages/flet/lib/src/utils/edge_insets.dart index 0a562b061..ec6eaa077 100644 --- a/packages/flet/lib/src/utils/edge_insets.dart +++ b/packages/flet/lib/src/utils/edge_insets.dart @@ -1,69 +1,102 @@ -import 'dart:convert'; - import 'package:flutter/widgets.dart'; import '../models/control.dart'; import 'material_state.dart'; import 'numbers.dart'; -EdgeInsets? parseEdgeInsets(Control control, String propName, - [EdgeInsets? defaultValue]) { - var v = control.attrString(propName, null); - if (v == null) { - return defaultValue; +EdgeInsets? parseEdgeInsets(dynamic value, [EdgeInsets? defaultValue]) { + if (value == null) return defaultValue; + if (value is int || value is double) { + return EdgeInsets.all(parseDouble(value, 0)!); } + return EdgeInsets.fromLTRB( + parseDouble(value['left'], 0)!, + parseDouble(value['top'], 0)!, + parseDouble(value['right'], 0)!, + parseDouble(value['bottom'], 0)!); +} - final j1 = json.decode(v); - return edgeInsetsFromJson(j1, defaultValue); +EdgeInsets? parseMargin(dynamic value, [EdgeInsets? defaultValue]) { + return parseEdgeInsets(value, defaultValue); } -EdgeInsets? edgeInsetsFromJson(dynamic json, [EdgeInsets? defaultValue]) { - if (json == null) { - return defaultValue; - } else if (json is int || json is double) { - return EdgeInsets.all(parseDouble(json, 0)!); - } - return EdgeInsets.fromLTRB( - parseDouble(json['l'], 0)!, - parseDouble(json['t'], 0)!, - parseDouble(json['r'], 0)!, - parseDouble(json['b'], 0)!); +EdgeInsets? parsePadding(dynamic value, [EdgeInsets? defaultValue]) { + return parseEdgeInsets(value, defaultValue); } -EdgeInsetsDirectional? parseEdgeInsetsDirectional( - Control control, String propName, +EdgeInsetsDirectional? parseEdgeInsetsDirectional(dynamic value, [EdgeInsetsDirectional? defaultValue]) { - var v = control.attrString(propName, null); - if (v == null) { - return defaultValue; + if (value == null) return defaultValue; + if (value is int || value is double) { + return EdgeInsetsDirectional.all(parseDouble(value, 0)!); } + return EdgeInsetsDirectional.fromSTEB( + parseDouble(value['left'], 0)!, + parseDouble(value['top'], 0)!, + parseDouble(value['right'], 0)!, + parseDouble(value['bottom'], 0)!); +} - final j1 = json.decode(v); - return edgeInsetsDirectionalFromJson(j1, defaultValue); +WidgetStateProperty? parseWidgetStateEdgeInsets(dynamic value, + {EdgeInsets? defaultEdgeInsets, + WidgetStateProperty? defaultValue}) { + if (value == null) return defaultValue; + + return getWidgetStateProperty( + value, (jv) => parseEdgeInsets(jv), defaultEdgeInsets); } -EdgeInsetsDirectional? edgeInsetsDirectionalFromJson(dynamic json, - [EdgeInsetsDirectional? defaultValue]) { - if (json == null) { - return defaultValue; - } else if (json is int || json is double) { - return EdgeInsetsDirectional.all(parseDouble(json, 0)!); - } - return EdgeInsetsDirectional.fromSTEB( - parseDouble(json['l'], 0)!, - parseDouble(json['t'], 0)!, - parseDouble(json['r'], 0)!, - parseDouble(json['b'], 0)!); +WidgetStateProperty? parseWidgetStatePadding(dynamic value, + {EdgeInsets? defaultPadding, + WidgetStateProperty? defaultValue}) { + return parseWidgetStateEdgeInsets(value, + defaultEdgeInsets: defaultPadding, defaultValue: defaultValue); +} + +WidgetStateProperty? parseWidgetStateMargin(dynamic value, + {EdgeInsets? defaultEdgeInsets, + WidgetStateProperty? defaultValue}) { + return parseWidgetStateEdgeInsets(value, + defaultEdgeInsets: defaultEdgeInsets, defaultValue: defaultValue); } -WidgetStateProperty? parseWidgetStateEdgeInsets( - Control control, String propName, - [EdgeInsets? defaultValue]) { - var v = control.attrString(propName, null); - if (v == null) { - return null; +extension EdgeInsetsParsers on Control { + EdgeInsets? getEdgeInsets(String propertyName, [EdgeInsets? defaultValue]) { + return parseEdgeInsets(get(propertyName), defaultValue); } - return getWidgetStateProperty( - jsonDecode(v), (jv) => edgeInsetsFromJson(jv), defaultValue); + EdgeInsets? getMargin(String propertyName, [EdgeInsets? defaultValue]) { + return parseMargin(get(propertyName), defaultValue); + } + + EdgeInsets? getPadding(String propertyName, [EdgeInsets? defaultValue]) { + return parsePadding(get(propertyName), defaultValue); + } + + EdgeInsetsDirectional? getEdgeInsetsDirectional(String propertyName, + [EdgeInsetsDirectional? defaultValue]) { + return parseEdgeInsetsDirectional(get(propertyName), defaultValue); + } + + WidgetStateProperty? getWidgetStateEdgeInsets( + String propertyName, + {EdgeInsets? defaultEdgeInsets, + WidgetStateProperty? defaultValue}) { + return parseWidgetStateEdgeInsets(get(propertyName), + defaultEdgeInsets: defaultEdgeInsets, defaultValue: defaultValue); + } + + WidgetStateProperty? getWidgetStatePadding(String propertyName, + {EdgeInsets? defaultPadding, + WidgetStateProperty? defaultValue}) { + return parseWidgetStatePadding(get(propertyName), + defaultPadding: defaultPadding, defaultValue: defaultValue); + } + + WidgetStateProperty? getWidgetStateMargin(String propertyName, + {EdgeInsets? defaultEdgeInsets, + WidgetStateProperty? defaultValue}) { + return parseWidgetStateMargin(get(propertyName), + defaultEdgeInsets: defaultEdgeInsets, defaultValue: defaultValue); + } } diff --git a/packages/flet/lib/src/utils/events.dart b/packages/flet/lib/src/utils/events.dart new file mode 100644 index 000000000..3394f25e1 --- /dev/null +++ b/packages/flet/lib/src/utils/events.dart @@ -0,0 +1,161 @@ +import 'package:flutter/gestures.dart'; + +extension ScaleEndDetailsExtension on ScaleEndDetails { + Map toMap() => { + "pc": pointerCount, + "vx": velocity.pixelsPerSecond.dx, + "vy": velocity.pixelsPerSecond.dy, + }; +} + +extension ScaleUpdateDetailsExtension on ScaleUpdateDetails { + Map toMap() => { + "fpx": focalPoint.dx, + "fpy": focalPoint.dy, + "fpdx": focalPointDelta.dx, + "fpdy": focalPointDelta.dy, + "lfpx": localFocalPoint.dx, + "lfpy": localFocalPoint.dy, + "pc": pointerCount, + "hs": horizontalScale, + "vs": verticalScale, + "s": scale, + "rot": rotation, + "ts": sourceTimeStamp, + }; +} + +extension ScaleStartDetailsExtension on ScaleStartDetails { + Map toMap() => { + "fpx": focalPoint.dx, + "fpy": focalPoint.dy, + "lfpx": localFocalPoint.dx, + "lfpy": localFocalPoint.dy, + "pc": pointerCount, + "ts": sourceTimeStamp, + }; +} + +extension DragEndDetailsExtension on DragEndDetails { + Map toMap() => { + "lx": localPosition.dx, + "ly": localPosition.dy, + "gx": globalPosition.dx, + "gy": globalPosition.dy, + "vx": velocity.pixelsPerSecond.dx, + "vy": velocity.pixelsPerSecond.dy, + "pv": primaryVelocity, + }; +} + +extension LongPressEndDetailsExtension on LongPressEndDetails { + Map toMap() => { + "lx": localPosition.dx, + "ly": localPosition.dy, + "gx": globalPosition.dx, + "gy": globalPosition.dy, + "vx": velocity.pixelsPerSecond.dx, + "vy": velocity.pixelsPerSecond.dy, + }; +} + +extension LongPressStartDetailsExtension on LongPressStartDetails { + Map toMap() => { + "lx": localPosition.dx, + "ly": localPosition.dy, + "gx": globalPosition.dx, + "gy": globalPosition.dy, + }; +} + +extension TapDownDetailsExtension on TapDownDetails { + Map toMap() => { + "k": kind?.name, + "lx": localPosition.dx, + "ly": localPosition.dy, + "gx": globalPosition.dx, + "gy": globalPosition.dy, + }; +} + +extension TapUpDetailsExtension on TapUpDetails { + Map toMap() => { + "k": kind.name, + "lx": localPosition.dx, + "ly": localPosition.dy, + "gx": globalPosition.dx, + "gy": globalPosition.dy, + }; +} + +extension DragStartDetailsExtension on DragStartDetails { + Map toMap() => { + "k": kind?.name, + "lx": localPosition.dx, + "ly": localPosition.dy, + "gx": globalPosition.dx, + "gy": globalPosition.dy, + "ts": sourceTimeStamp, + }; +} + +extension DragUpdateDetailsExtension on DragUpdateDetails { + Map toMap(double previousX, double previousY) { + return { + "lx": localPosition.dx, + "ly": localPosition.dy, + "gx": globalPosition.dx, + "gy": globalPosition.dy, + "ts": sourceTimeStamp, + "dx": localPosition.dx - previousX, + "dy": localPosition.dy - previousY, + "pd": primaryDelta, + }; + } +} + +extension PointerEventExtension on PointerEvent { + Map toMap([double? previousDx, double? previousDy]) { + // todo: should dx/dy be null if previousDx/Dy is null? + var dx = previousDx != null ? previousDx - localPosition.dx : null; + var dy = previousDy != null ? previousDy - localPosition.dy : null; + return { + "k": kind.name, + "lx": localPosition.dx, + "ly": localPosition.dy, + "gx": position.dx, + "gy": position.dy, + "ts": timeStamp, + "vId": viewId, + "btt": buttons, + "obs": obscured, + "dev": device, + "ps": pressure, + "pMin": pressureMin, + "pMax": pressureMax, + "dist": distance, + "distMax": distanceMax, + "size": size, + "rMj": radiusMajor, + "rMn": radiusMinor, + "rMin": radiusMin, + "rMax": radiusMax, + "or": orientation, + "tilt": tilt, + "eId": embedderId, + "dx": dx, + "dy": dy, + }; + } +} + +extension PointerScrollEventExtension on PointerScrollEvent { + Map toMap() => { + "lx": localPosition.dx, + "ly": localPosition.dy, + "gx": position.dx, + "gy": position.dy, + "sdx": scrollDelta.dx, + "sdy": scrollDelta.dy, + }; +} diff --git a/packages/flet/lib/src/utils/file_picker.dart b/packages/flet/lib/src/utils/file_picker.dart new file mode 100644 index 000000000..bef62968a --- /dev/null +++ b/packages/flet/lib/src/utils/file_picker.dart @@ -0,0 +1,73 @@ +import 'package:collection/collection.dart'; +import 'package:file_picker/file_picker.dart'; + +import '../models/control.dart'; + +class FilePickerResultEvent { + final String? path; + final List? files; + + FilePickerResultEvent({required this.path, required this.files}); + + Map toMap() => { + 'path': path, + 'files': files?.map((FilePickerFile f) => f.toMap()).toList() + }; +} + +class FilePickerFile { + final int id; + final String name; + final String? path; + final int size; + + FilePickerFile( + {required this.id, + required this.name, + required this.path, + required this.size}); + + Map toMap() => + {'id': id, 'name': name, 'path': path, 'size': size}; +} + +class FilePickerUploadFile { + final int? id; + final String? name; + final String uploadUrl; + final String method; + + FilePickerUploadFile( + {required this.id, + required this.name, + required this.uploadUrl, + required this.method}); +} + +class FilePickerUploadProgressEvent { + final String name; + final double? progress; + final String? error; + + FilePickerUploadProgressEvent( + {required this.name, required this.progress, required this.error}); + + Map toMap() => { + 'file_name': name, + 'progress': progress, + 'error': error + }; +} + +FileType? parseFileType(String? value, [FileType? defaultValue]) { + if (value == null) return defaultValue; + return FileType.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +extension FilePickerParsers on Control { + FileType? getFileType(String propertyName, [FileType? defaultValue]) { + return parseFileType(get(propertyName), defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/form_field.dart b/packages/flet/lib/src/utils/form_field.dart index 246a2ceed..4cbeda33f 100644 --- a/packages/flet/lib/src/utils/form_field.dart +++ b/packages/flet/lib/src/utils/form_field.dart @@ -1,15 +1,13 @@ -import 'dart:convert'; - import 'package:collection/collection.dart'; +import 'package:flet/src/extensions/control.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../controls/create_control.dart'; import '../models/control.dart'; +import '../utils/colors.dart'; import 'borders.dart'; import 'box.dart'; import 'edge_insets.dart'; -import 'icons.dart'; import 'numbers.dart'; import 'text.dart'; import 'time.dart'; @@ -17,16 +15,15 @@ import 'time.dart'; enum FormFieldInputBorder { outline, underline, none } FormFieldInputBorder? parseFormFieldInputBorder(String? value, - [FormFieldInputBorder? defValue]) { - if (value == null) { - return defValue; - } + [FormFieldInputBorder? defaultValue]) { + if (value == null) return defaultValue; return FormFieldInputBorder.values.firstWhereOrNull( (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; + defaultValue; } -TextInputType? parseTextInputType(String? value, [TextInputType? defValue]) { +TextInputType? parseTextInputType(String? value, + [TextInputType? defaultValue]) { switch (value?.toLowerCase()) { case "datetime": return TextInputType.datetime; @@ -51,67 +48,91 @@ TextInputType? parseTextInputType(String? value, [TextInputType? defValue]) { case "visiblepassword": return TextInputType.visiblePassword; default: - return defValue; + return defaultValue; } } -InputDecoration buildInputDecoration(BuildContext context, Control control, - {Control? prefix, - Control? prefixIcon, - Control? suffix, - Control? suffixIcon, - Control? icon, - Control? counter, - Control? error, - Control? helper, - Control? label, - Widget? customSuffix, - int? valueLength, - int? maxLength, - bool focused = false, - bool disabled = false, - bool? adaptive}) { +InputDecoration buildInputDecoration( + BuildContext context, + Control control, { + Widget? customSuffix, + int? valueLength, + int? maxLength, + bool focused = false, +}) { FormFieldInputBorder inputBorder = parseFormFieldInputBorder( - control.attrString("border"), + control.getString("border"), FormFieldInputBorder.outline, )!; - var iconStr = parseIcon(control.attrString("icon")); - var prefixIconData = parseIcon(control.attrString("prefixIcon")); - var prefixIconWidget = prefixIcon != null - ? createControl(control, prefixIcon.id, control.isDisabled, - parentAdaptive: adaptive) - : (prefixIconData != null ? Icon(prefixIconData) : null); - var suffixIconData = parseIcon(control.attrString("suffixIcon")); - var suffixIconWidget = suffixIcon != null - ? createControl(control, suffixIcon.id, control.isDisabled, - parentAdaptive: adaptive) - : (suffixIconData != null ? Icon(suffixIconData) : null); - var prefixText = control.attrString("prefixText"); - var suffixText = control.attrString("suffixText"); + var bgcolor = control.getColor("bgcolor", context); + var focusedBgcolor = control.getColor("focused_bgcolor", context); + var fillColor = control.getColor("fill_color", context); + var hoverColor = control.getColor("hover_color", context); + var borderColor = control.getColor("border_color", context); + var borderRadius = control.getBorderRadius("border_radius"); + var focusedBorderColor = control.getColor("focused_border_color", context); + var borderWidth = control.getDouble("border_width"); + var focusedBorderWidth = control.getDouble("focused_border_width"); - var bgcolor = control.attrColor("bgcolor", context); - var focusedBgcolor = control.attrColor("focusedBgcolor", context); - var fillColor = control.attrColor("fillColor", context); - var hoverColor = control.attrColor("hoverColor", context); - var borderColor = control.attrColor("borderColor", context); + //counter + String? counterText; + Widget? counterWidget; + var counter = control.get("counter"); + if (counter is Control) { + counterWidget = control.buildWidget("counter"); + } else { + counterText = control + .getString("counter") + ?.replaceAll("{value_length}", valueLength.toString()) + .replaceAll("{max_length}", maxLength?.toString() ?? "None") + .replaceAll("{symbols_left}", + "${maxLength == null ? 'None' : (maxLength - (valueLength ?? 0))}"); + } - var borderRadius = parseBorderRadius(control, "borderRadius"); - var focusedBorderColor = control.attrColor("focusedBorderColor", context); - var borderWidth = control.attrDouble("borderWidth"); - var focusedBorderWidth = control.attrDouble("focusedBorderWidth"); + // error + String? errorText; + Widget? errorWidget; + var error = control.get("error"); + if (error is Control) { + errorWidget = control.buildWidget("error"); + } else { + errorText = control.getString("error"); + } + // helper + String? helperText; + Widget? helperWidget; + var helper = control.get("helper"); + if (helper is Control) { + helperWidget = control.buildWidget("helper"); + } else { + helperText = control.getString("helper"); + } - var counterText = control - .attrString("counterText", "") - ?.replaceAll("{value_length}", valueLength.toString()) - .replaceAll("{max_length}", maxLength?.toString() ?? "None") - .replaceAll("{symbols_left}", - "${maxLength == null ? 'None' : (maxLength - (valueLength ?? 0))}"); + // prefix + String? prefixText; + Widget? prefixWidget; + var prefix = control.get("prefix"); + if (prefix is Control) { + prefixWidget = control.buildWidget("prefix"); + } else { + prefixText = control.getString("prefix"); + } + + // suffix + String? suffixText; + Widget? suffixWidget; + var suffix = control.get("suffix"); + if (suffix is Control) { + suffixWidget = control.buildWidget("suffix"); + } else { + suffixText = control.getString("suffix"); + } InputBorder? border; if (inputBorder == FormFieldInputBorder.underline) { border = UnderlineInputBorder( borderSide: BorderSide( - color: borderColor ?? Color(0xFF000000), + color: borderColor ?? const Color(0xFF000000), width: borderWidth ?? 1.0)); } else if (inputBorder == FormFieldInputBorder.none) { border = InputBorder.none; @@ -121,7 +142,7 @@ InputDecoration buildInputDecoration(BuildContext context, Control control, borderWidth != null) { border = OutlineInputBorder( borderSide: BorderSide( - color: borderColor ?? Color(0xFF000000), + color: borderColor ?? const Color(0xFF000000), width: borderWidth ?? 1.0)); if (borderRadius != null) { border = @@ -133,7 +154,10 @@ InputDecoration buildInputDecoration(BuildContext context, Control control, ? BorderSide.none : BorderSide( color: borderColor ?? - Theme.of(context).colorScheme.onSurface.withOpacity(0.38), + Theme.of(context) + .colorScheme + .onSurface + .withAlpha((255.0 * 0.38).round()), width: borderWidth ?? 1.0)); } } @@ -154,115 +178,96 @@ InputDecoration buildInputDecoration(BuildContext context, Control control, } return InputDecoration( - enabled: !disabled, - contentPadding: parseEdgeInsets(control, "contentPadding"), - isDense: control.attrBool("dense"), - label: label != null - ? createControl(control, label.id, control.isDisabled, - parentAdaptive: adaptive) - : control.attrString("label") != null - ? Text(control.attrString("label")!) - : null, - labelStyle: parseTextStyle(Theme.of(context), control, "labelStyle"), + enabled: !control.disabled, + contentPadding: control.getEdgeInsets("content_padding"), + isDense: control.getBool("dense"), + label: control.buildTextOrWidget("label"), + labelStyle: control.getTextStyle("label_style", Theme.of(context)), border: border, enabledBorder: border, focusedBorder: focusedBorder, hoverColor: hoverColor, - icon: icon != null - ? createControl(control, icon.id, control.isDisabled, - parentAdaptive: adaptive) - : iconStr != null - ? Icon(iconStr) - : null, - filled: control.attrBool("filled", false)!, - fillColor: fillColor ?? (focused ? focusedBgcolor ?? bgcolor : bgcolor), - hintText: control.attrString("hintText"), - hintStyle: parseTextStyle(Theme.of(context), control, "hintStyle"), - helperText: control.attrString("helperText"), - helperStyle: parseTextStyle(Theme.of(context), control, "helperStyle"), + icon: control.buildIconOrWidget("icon"), + filled: control.getBool("filled", false)!, + fillColor: fillColor ?? (focused ? (focusedBgcolor ?? bgcolor) : bgcolor), + //hint + hintText: control.getString("hint_text"), + hintStyle: control.getTextStyle("hint_style", Theme.of(context)), + hintFadeDuration: control.getDuration("hint_fade_duration"), + hintMaxLines: control.getInt("hint_max_lines"), + //helper + helper: helperWidget, + helperText: helperText, + helperStyle: control.getTextStyle("helper_style", Theme.of(context)), + helperMaxLines: control.getInt("helper_max_lines"), + //counter + counter: counterWidget, counterText: counterText, - counterStyle: parseTextStyle(Theme.of(context), control, "counterStyle"), - counter: counter != null - ? createControl(control, counter.id, control.isDisabled, - parentAdaptive: adaptive) - : null, - error: error != null - ? createControl(control, error.id, control.isDisabled, - parentAdaptive: adaptive) - : null, - helper: helper != null - ? createControl(control, helper.id, control.isDisabled, - parentAdaptive: adaptive) - : null, - constraints: parseBoxConstraints(control, "sizeConstraints"), - isCollapsed: control.attrBool("collapsed"), + counterStyle: control.getTextStyle("counter_style", Theme.of(context)), + //error + error: errorWidget, + errorText: errorText, + errorStyle: control.getTextStyle("error_style", Theme.of(context)), + errorMaxLines: control.getInt("error_max_lines"), + constraints: control.getBoxConstraints("size_constraints"), + isCollapsed: control.getBool("collapsed"), prefixIconConstraints: - parseBoxConstraints(control, "prefixIconConstraints"), + control.getBoxConstraints("prefix_icon_constraints"), suffixIconConstraints: - parseBoxConstraints(control, "suffixIconConstraints"), - focusColor: control.attrColor("focusColor", context), - errorMaxLines: control.attrInt("errorMaxLines"), - alignLabelWithHint: control.attrBool("alignLabelWithHint"), - errorText: control.attrString("errorText"), - errorStyle: parseTextStyle(Theme.of(context), control, "errorStyle"), - prefixIcon: prefixIconWidget, - prefixText: - prefix == null ? prefixText : null, // ignored if prefix is set - hintFadeDuration: parseDuration(control, "hintFadeDuration"), - hintMaxLines: control.attrInt("hintMaxLines"), - helperMaxLines: control.attrInt("helperMaxLines"), - prefixStyle: parseTextStyle(Theme.of(context), control, "prefixStyle"), - prefix: prefix != null - ? createControl(control, prefix.id, control.isDisabled, - parentAdaptive: adaptive) - : null, - suffix: suffix != null - ? createControl(control, suffix.id, control.isDisabled, - parentAdaptive: adaptive) - : null, - suffixIcon: suffixIconWidget ?? customSuffix, - suffixText: - suffix == null ? suffixText : null, // ignored if suffix is set - suffixStyle: parseTextStyle(Theme.of(context), control, "suffixStyle")); + control.getBoxConstraints("suffix_icon_constraints"), + focusColor: control.getColor("focus_color", context), + alignLabelWithHint: control.getBool("align_label_with_hint"), + prefixIcon: control.buildIconOrWidget("prefix_icon"), + //prefix + prefix: prefixWidget, + prefixText: prefixText, + prefixStyle: control.getTextStyle("prefix_style", Theme.of(context)), + suffixIcon: control.buildIconOrWidget("suffix_icon") ?? customSuffix, + //suffix + suffix: suffixWidget, + suffixText: suffixText, + suffixStyle: control.getTextStyle("suffix_style", Theme.of(context))); } -OverlayVisibilityMode? parseVisibilityMode(String? value, - [OverlayVisibilityMode? defValue]) { - switch (value?.toLowerCase()) { - case "never": - return OverlayVisibilityMode.never; - case "notediting": - return OverlayVisibilityMode.notEditing; - case "editing": - return OverlayVisibilityMode.editing; - case "always": - return OverlayVisibilityMode.always; - } - return defValue; +OverlayVisibilityMode? parseOverlayVisibilityMode(String? value, + [OverlayVisibilityMode? defaultValue]) { + if (value == null) return defaultValue; + return OverlayVisibilityMode.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; } -StrutStyle? parseStrutStyle(Control control, String propName) { - dynamic j; - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - j = json.decode(v); - return strutStyleFromJson(j); +StrutStyle? parseStrutStyle(dynamic value, [StrutStyle? defaultValue]) { + if (value == null) return defaultValue; + + return StrutStyle( + fontSize: parseDouble(value["size"]), + fontWeight: getFontWeight(value["weight"]), + fontStyle: parseBool(value["italic"], false)! ? FontStyle.italic : null, + fontFamily: value["font_family"], + height: parseDouble(value["height"]), + leading: parseDouble(value["leading"]), + forceStrutHeight: parseBool(value["force_strut_height"]), + ); } -StrutStyle? strutStyleFromJson(Map? json) { - if (json == null) { - return null; +extension FormFieldParsers on Control { + FormFieldInputBorder? getFormFieldInputBorder(String propertyName, + [FormFieldInputBorder? defaultValue]) { + return parseFormFieldInputBorder(get(propertyName), defaultValue); } - return StrutStyle( - fontSize: parseDouble(json["size"]), - fontWeight: getFontWeight(json["weight"]), - fontStyle: parseBool(json["italic"], false)! ? FontStyle.italic : null, - fontFamily: json["font_family"], - height: parseDouble(json["height"]), - leading: parseDouble(json["leading"]), - forceStrutHeight: parseBool(json["force_strut_height"]), - ); + TextInputType? getTextInputType(String propertyName, + [TextInputType? defaultValue]) { + return parseTextInputType(get(propertyName), defaultValue); + } + + OverlayVisibilityMode? getOverlayVisibilityMode(String propertyName, + [OverlayVisibilityMode? defaultValue]) { + return parseOverlayVisibilityMode(get(propertyName), defaultValue); + } + + StrutStyle? getStrutStyle(String propertyName, [StrutStyle? defaultValue]) { + return parseStrutStyle(get(propertyName), defaultValue); + } } diff --git a/packages/flet/lib/src/utils/geometry.dart b/packages/flet/lib/src/utils/geometry.dart new file mode 100644 index 000000000..fc47b6c53 --- /dev/null +++ b/packages/flet/lib/src/utils/geometry.dart @@ -0,0 +1,40 @@ +import 'package:flutter/animation.dart'; + +import '../models/control.dart'; +import 'numbers.dart'; + +Size? parseSize(dynamic value, [Size? defaultValue]) { + if (value == null) return defaultValue; + + final width = parseDouble(value['width']); + final height = parseDouble(value['height']); + + if (width == null || height == null) return defaultValue; + + return Size(width, height); +} + +Rect? parseRect(dynamic value, [Rect? defaultValue]) { + if (value == null) return defaultValue; + + final left = parseDouble(value['left']); + final top = parseDouble(value['top']); + final right = parseDouble(value['right']); + final bottom = parseDouble(value['bottom']); + + if (left == null || top == null || right == null || bottom == null) { + return defaultValue; + } + + return Rect.fromLTRB(left, top, right, bottom); +} + +extension GeometryParsers on Control { + Size? getSize(String propertyName, [Size? defaultValue]) { + return parseSize(get(propertyName), defaultValue); + } + + Rect? getRect(String propertyName, [Rect? defaultValue]) { + return parseRect(get(propertyName), defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/gesture_detector.dart b/packages/flet/lib/src/utils/gesture_detector.dart new file mode 100644 index 000000000..e1d4df68e --- /dev/null +++ b/packages/flet/lib/src/utils/gesture_detector.dart @@ -0,0 +1,54 @@ +import 'package:flutter/gestures.dart'; + +class MultiTouchGestureRecognizer extends MultiTapGestureRecognizer { + late MultiTouchGestureRecognizerCallback onMultiTap; + var numberOfTouches = 0; + int minNumberOfTouches = 0; + + MultiTouchGestureRecognizer() { + super.onTapDown = (pointer, details) => addTouch(pointer, details); + super.onTapUp = (pointer, details) => removeTouch(pointer, details); + super.onTapCancel = (pointer) => cancelTouch(pointer); + super.onTap = (pointer) => captureDefaultTap(pointer); + } + + void addTouch(int pointer, TapDownDetails details) { + //debugPrint("Add touch: $pointer"); + numberOfTouches++; + if (numberOfTouches == minNumberOfTouches) { + onMultiTap(true); + numberOfTouches = 0; + } + } + + void removeTouch(int pointer, TapUpDetails details) { + onRemoveTouch(pointer); + } + + void cancelTouch(int pointer) { + onRemoveTouch(pointer); + } + + void onRemoveTouch(int pointer) { + //debugPrint("Remove touch: $pointer"); + onMultiTap(false); + numberOfTouches = 0; + } + + void captureDefaultTap(int pointer) {} + + @override + set onTapDown(onTapDown) {} + + @override + set onTapUp(onTapUp) {} + + @override + set onTapCancel(onTapCancel) {} + + @override + set onTap(onTap) {} +} + +typedef MultiTouchGestureRecognizerCallback = void Function( + bool correctNumberOfTouches); diff --git a/packages/flet/lib/src/utils/gradient.dart b/packages/flet/lib/src/utils/gradient.dart index b148c77a3..df5ce8398 100644 --- a/packages/flet/lib/src/utils/gradient.dart +++ b/packages/flet/lib/src/utils/gradient.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:typed_data'; import 'package:collection/collection.dart'; @@ -9,85 +8,72 @@ import 'alignment.dart'; import 'colors.dart'; import 'numbers.dart'; -Gradient? parseGradient(ThemeData theme, Control control, String propName) { - var v = control.attrString(propName); - if (v == null) { - return null; - } - - final j1 = json.decode(v); - return gradientFromJSON(theme, j1); -} +Gradient? parseGradient(dynamic value, ThemeData theme, + [Gradient? defaultValue]) { + if (value == null) return defaultValue; -Gradient? gradientFromJSON(ThemeData? theme, Map? json) { - if (json == null) { - return null; - } - String type = json["type"]; + var type = value["_type"]; + var colors = parseColors(value["colors"], theme); + var stops = parseGradientStops(value["stops"]); + var rotation = parseRotation(value["rotation"]); if (type == "linear") { return LinearGradient( - colors: parseColors(theme, json["colors"]), - stops: parseStops(json["stops"]), - begin: alignmentFromJson(json["begin"], Alignment.centerLeft)!, - end: alignmentFromJson(json["end"], Alignment.centerRight)!, - tileMode: parseTileMode(json["tile_mode"], TileMode.clamp)!, - transform: parseRotation(json["rotation"])); + colors: colors, + stops: stops, + begin: parseAlignment(value["begin"], Alignment.centerLeft)!, + end: parseAlignment(value["end"], Alignment.centerRight)!, + tileMode: parseTileMode(value["tile_mode"], TileMode.clamp)!, + transform: rotation); } else if (type == "radial") { return RadialGradient( - colors: parseColors(theme, json["colors"]), - stops: parseStops(json["stops"]), - center: alignmentFromJson(json["center"], Alignment.center)!, - radius: parseDouble(json["radius"], 0.5)!, - focalRadius: parseDouble(json["focal_radius"], 0)!, - focal: alignmentFromJson(json["focal"]), - tileMode: parseTileMode(json["tile_mode"], TileMode.clamp)!, - transform: parseRotation(json["rotation"])); + colors: colors, + stops: stops, + center: parseAlignment(value["center"], Alignment.center)!, + radius: parseDouble(value["radius"], 0.5)!, + focalRadius: parseDouble(value["focal_radius"], 0)!, + focal: parseAlignment(value["focal"]), + tileMode: parseTileMode(value["tile_mode"], TileMode.clamp)!, + transform: rotation); } else if (type == "sweep") { return SweepGradient( - colors: parseColors(theme, json["colors"]), - center: alignmentFromJson(json["center"], Alignment.center)!, - startAngle: parseDouble(json["start_angle"], 0)!, - endAngle: parseDouble(json["end_angle"], 0)!, - stops: parseStops(json["stops"]), - tileMode: parseTileMode(json["tile_mode"], TileMode.clamp)!, - transform: parseRotation(json["rotation"])); + colors: colors, + center: parseAlignment(value["center"], Alignment.center)!, + startAngle: parseDouble(value["start_angle"], 0)!, + endAngle: parseDouble(value["end_angle"], 0)!, + stops: stops, + tileMode: parseTileMode(value["tile_mode"], TileMode.clamp)!, + transform: rotation); } - return null; + return defaultValue; } -List parseColors(ThemeData? theme, dynamic jv) { - return (jv as List).map((c) => parseColor(theme, c as String)!).toList(); +List parseColors(dynamic value, ThemeData? theme) { + return (value as List).map((c) => parseColor(c as String, theme)!).toList(); } -List? parseStops(dynamic jv) { - if (jv == null) { - return null; - } - List? list = jv as List; - if (list.isEmpty) { - return null; - } - return list.map((v) => parseDouble(v)).whereNotNull().toList(); +List? parseGradientStops(dynamic value, [List? defaultValue]) { + if (value == null) return defaultValue; + List? valueAsList = value as List; + if (valueAsList.isEmpty) return defaultValue; + return valueAsList.map((v) => parseDouble(v)).nonNulls.toList(); } -TileMode? parseTileMode(dynamic jv, [TileMode? defValue]) { - return TileMode.values - .firstWhereOrNull((e) => e.name.toLowerCase() == jv.toLowerCase()) ?? - defValue; +TileMode? parseTileMode(String? value, [TileMode? defaultValue]) { + if (value == null) return defaultValue; + return TileMode.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; } -GradientRotation? parseRotation(dynamic jv, [GradientRotation? defValue]) { - if (jv == null) { - return defValue; - } - return GradientRotation(parseDouble(jv, 0)!); +GradientRotation? parseRotation(dynamic value, + [GradientRotation? defaultValue]) { + if (value == null) return defaultValue; + return GradientRotation(parseDouble(value, 0)!); } -Float64List? parseRotationToMatrix4(dynamic jv, Rect bounds) { - if (jv == null) { - return null; - } - return GradientRotation(parseDouble(jv, 0)!).transform(bounds).storage; +Float64List? parseRotationToMatrix4(dynamic value, Rect bounds) { + if (value == null) return null; + return GradientRotation(parseDouble(value, 0)!).transform(bounds).storage; } extension GradientExtension on Gradient { @@ -138,3 +124,27 @@ Color lerpGradient(List colors, List stops, double t) { } return colors.last; } + +extension GradientParsers on Control { + Gradient? getGradient(String propertyName, ThemeData theme) { + return parseGradient(get(propertyName), theme); + } + + List getColors(String propertyName, ThemeData theme) { + return parseColors(get(propertyName), theme); + } + + List? getGradientStops(String propertyName, + [List? defaultValue]) { + return parseGradientStops(get(propertyName), defaultValue); + } + + TileMode? getTileMode(String propertyName, [TileMode? defaultValue]) { + return parseTileMode(get(propertyName), defaultValue); + } + + GradientRotation? getGradientRotation(String propertyName, + [GradientRotation? defaultValue]) { + return parseRotation(get(propertyName), defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/icons.dart b/packages/flet/lib/src/utils/icons.dart index 2d2b82af9..5b1643fb4 100644 --- a/packages/flet/lib/src/utils/icons.dart +++ b/packages/flet/lib/src/utils/icons.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; import '../models/control.dart'; @@ -7,22 +5,31 @@ import 'cupertino_icons.dart'; import 'material_icons.dart'; import 'material_state.dart'; -IconData? parseIcon(String? iconName, [IconData? defaultIcon]) { - if (iconName == null) { - return defaultIcon; - } - return materialIcons[iconName.toLowerCase()] ?? cupertinoIcons[iconName.toLowerCase()]; +IconData? parseIcon(String? value, [IconData? defaultValue]) { + if (value == null) return defaultValue; + return materialIcons[value.toLowerCase()] ?? + cupertinoIcons[value.toLowerCase()]; } -WidgetStateProperty? parseWidgetStateIcon( - ThemeData theme, Control control, String propName) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } +WidgetStateProperty? parseWidgetStateIcon(dynamic value, + ThemeData theme, { + Icon? defaultIcon, + WidgetStateProperty? defaultValue, + }) { + if (value == null) return defaultValue; + return getWidgetStateProperty( + value, (jv) => Icon(parseIcon(jv as String)), defaultIcon); +} - final j1 = json.decode(v); +extension IconParsers on Control { + IconData? getIcon(String propertyName, [IconData? defaultValue]) { + return parseIcon(get(propertyName), defaultValue); + } - return getWidgetStateProperty( - j1, (jv) => Icon(parseIcon(jv as String))); + WidgetStateProperty? getWidgetStateIcon( + String propertyName, ThemeData theme, + {Icon? defaultIcon, WidgetStateProperty? defaultValue}) { + return parseWidgetStateIcon(get(propertyName), theme, + defaultIcon: defaultIcon, defaultValue: defaultValue); + } } diff --git a/packages/flet/lib/src/utils/images.dart b/packages/flet/lib/src/utils/images.dart index b4602abf3..c87b867b7 100644 --- a/packages/flet/lib/src/utils/images.dart +++ b/packages/flet/lib/src/utils/images.dart @@ -9,111 +9,77 @@ import 'colors.dart'; import 'gradient.dart'; import 'numbers.dart'; -export 'images_io.dart' if (dart.library.js) "images_web.dart"; +export "images_web.dart" if (dart.library.io) 'images_io.dart'; -ImageRepeat? parseImageRepeat(String? repeat, [ImageRepeat? defValue]) { - if (repeat == null) { - return defValue; - } +ImageRepeat? parseImageRepeat(String? value, [ImageRepeat? defaultValue]) { + if (value == null) return defaultValue; return ImageRepeat.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == repeat.toLowerCase()) ?? - defValue; + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; } -BlendMode? parseBlendMode(String? mode, [BlendMode? defValue]) { - if (mode == null) { - return defValue; - } +BlendMode? parseBlendMode(String? value, [BlendMode? defaultValue]) { + if (value == null) return defaultValue; return BlendMode.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == mode.toLowerCase()) ?? - defValue; + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; } -BoxFit? parseBoxFit(String? fit, [BoxFit? defValue]) { - if (fit == null) { - return defValue; - } - return BoxFit.values - .firstWhereOrNull((e) => e.name.toLowerCase() == fit.toLowerCase()) ?? - defValue; -} - -ImageFilter? parseBlur(Control control, String propName, - [ImageFilter? defValue]) { - var v = control.attrString(propName, null); - if (v == null) { - return defValue; - } - - final j1 = json.decode(v); - return blurImageFilterFromJSON(j1); +BoxFit? parseBoxFit(String? value, [BoxFit? defaultValue]) { + if (value == null) return defaultValue; + return BoxFit.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; } -ImageFilter? blurImageFilterFromJSON(dynamic json) { - if (json == null) return null; +ImageFilter? parseBlur(dynamic value, [ImageFilter? defaultValue]) { + if (value == null) return defaultValue; double sigmaX = 0.0, sigmaY = 0.0; TileMode? tileMode; - - if (json is num) { - sigmaX = sigmaY = parseDouble(json, 0)!; - } else if (json is List) { - sigmaX = parseDouble(json.isNotEmpty ? json[0] : 0, 0)!; - sigmaY = parseDouble(json.length > 1 ? json[1] : json[0], 0)!; - } else if (json is Map) { - sigmaX = parseDouble(json["sigma_x"], 0)!; - sigmaY = parseDouble(json["sigma_y"], 0)!; - tileMode = parseTileMode(json["tile_mode"]); + if (value is num) { + sigmaX = sigmaY = parseDouble(value, 0)!; + } else if (value is List) { + sigmaX = parseDouble(value.isNotEmpty ? value[0] : 0, 0)!; + sigmaY = parseDouble(value.length > 1 ? value[1] : value[0], 0)!; + } else if (value is Map) { + sigmaX = parseDouble(value["sigma_x"], 0)!; + sigmaY = parseDouble(value["sigma_y"], 0)!; + tileMode = parseTileMode(value["tile_mode"]); } return ImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); } -ColorFilter? parseColorFilter(Control control, String propName, ThemeData theme, - [ColorFilter? defValue]) { - var v = control.attrString(propName, null); - if (v == null) { - return defValue; - } - - final j1 = json.decode(v); - return colorFilterFromJSON(j1, theme); -} - -ColorFilter? colorFilterFromJSON(dynamic json, ThemeData theme, - [ColorFilter? defValue]) { - if (json == null) { - return defValue; - } - Color? color = parseColor(theme, json["color"]); - BlendMode? blendMode = parseBlendMode(json["blend_mode"]); - if (color == null || blendMode == null) { - return defValue; - } +ColorFilter? parseColorFilter(dynamic value, ThemeData theme, + [ColorFilter? defaultValue]) { + if (value == null) return defaultValue; + Color? color = parseColor(value["color"], theme); + BlendMode? blendMode = parseBlendMode(value["blend_mode"]); + if (color == null || blendMode == null) return defaultValue; return ColorFilter.mode(color, blendMode); } -FilterQuality? parseFilterQuality(String? quality, [FilterQuality? defValue]) { - if (quality == null) { - return defValue; - } +FilterQuality? parseFilterQuality(String? value, + [FilterQuality? defaultValue]) { + if (value == null) return defaultValue; return FilterQuality.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == quality.toLowerCase()) ?? - defValue; + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; } -bool isBase64ImageString(String s) { +bool isBase64ImageString(String value) { // Check for base64 prefix final base64PrefixPattern = RegExp(r'^data:image\/[a-zA-Z]+;base64,'); - if (base64PrefixPattern.hasMatch(s)) { + if (base64PrefixPattern.hasMatch(value)) { return true; } // Check if string contains only valid base64 characters and has a valid length (multiple of 4) final base64CharPattern = RegExp(r'^[A-Za-z0-9+/=]+$'); - if (base64CharPattern.hasMatch(s) && s.length % 4 == 0) { + if (base64CharPattern.hasMatch(value) && value.length % 4 == 0) { try { - base64.decode(s); + base64.decode(value); return true; } catch (e) { return false; @@ -123,18 +89,47 @@ bool isBase64ImageString(String s) { return false; } -bool isUrlOrPath(String s) { +bool isUrlOrPath(String value) { // Check for URL pattern final urlPattern = RegExp(r'^(http:\/\/|https:\/\/|www\.)'); - if (urlPattern.hasMatch(s)) { + if (urlPattern.hasMatch(value)) { return true; } // Check for common file path characters final filePathPattern = RegExp(r'^[a-zA-Z0-9_\-/\\\.]+$'); - if (filePathPattern.hasMatch(s)) { + if (filePathPattern.hasMatch(value)) { return true; } return false; } + +extension ImageParsers on Control { + ImageRepeat? getImageRepeat(String propertyName, + [ImageRepeat? defaultValue]) { + return parseImageRepeat(get(propertyName), defaultValue); + } + + BlendMode? getBlendMode(String propertyName, [BlendMode? defaultValue]) { + return parseBlendMode(get(propertyName), defaultValue); + } + + BoxFit? getBoxFit(String propertyName, [BoxFit? defaultValue]) { + return parseBoxFit(get(propertyName), defaultValue); + } + + ImageFilter? getBlur(String propertyName, [ImageFilter? defaultValue]) { + return parseBlur(get(propertyName), defaultValue); + } + + ColorFilter? getColorFilter(String propertyName, ThemeData theme, + [ColorFilter? defaultValue]) { + return parseColorFilter(get(propertyName), theme, defaultValue); + } + + FilterQuality? getFilterQuality(String propertyName, + [FilterQuality? defaultValue]) { + return parseFilterQuality(get(propertyName), defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/images_io.dart b/packages/flet/lib/src/utils/images_io.dart index 2aa2e1484..8a510ad1b 100644 --- a/packages/flet/lib/src/utils/images_io.dart +++ b/packages/flet/lib/src/utils/images_io.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:path/path.dart' as p; -import '../models/asset_src.dart'; +import '../models/asset_source.dart'; import 'uri.dart'; SvgPicture getSvgPictureFromFile( @@ -23,21 +23,21 @@ SvgPicture getSvgPictureFromFile( semanticsLabel: semanticsLabel); } -AssetSrc getAssetSrc(String src, Uri pageUri, String assetsDir) { +AssetSource getAssetSrc(String src, Uri pageUri, String assetsDir) { if (src.startsWith("http://") || src.startsWith("https://")) { - return AssetSrc(path: src, isFile: false); + return AssetSource(path: src, isFile: false); } else if (io.File(src).existsSync()) { - return AssetSrc(path: src, isFile: true); + return AssetSource(path: src, isFile: true); } else if (assetsDir != "") { var filePath = normalizePath(src); if (filePath.startsWith(p.separator)) { filePath = filePath.substring(1); } - return AssetSrc( + return AssetSource( path: p.join(normalizePath(assetsDir), filePath), isFile: true); } else { var uri = Uri.parse(src); - return AssetSrc( + return AssetSource( path: uri.hasAuthority ? src : getAssetUri(pageUri, src).toString(), isFile: false); } diff --git a/packages/flet/lib/src/utils/images_web.dart b/packages/flet/lib/src/utils/images_web.dart index 16705fbe1..879e43fd0 100644 --- a/packages/flet/lib/src/utils/images_web.dart +++ b/packages/flet/lib/src/utils/images_web.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import '../models/asset_src.dart'; +import '../models/asset_source.dart'; SvgPicture getSvgPictureFromFile( {required String src, @@ -14,8 +14,8 @@ SvgPicture getSvgPictureFromFile( return SvgPicture.string(""); } -AssetSrc getAssetSrc(String src, Uri pageUri, String assetsDir) { - return AssetSrc( +AssetSource getAssetSrc(String src, Uri pageUri, String assetsDir) { + return AssetSource( path: src.startsWith("/") ? src.substring(1) : src, isFile: false); } diff --git a/packages/flet/lib/src/utils/keys.dart b/packages/flet/lib/src/utils/keys.dart new file mode 100644 index 000000000..703cdcbac --- /dev/null +++ b/packages/flet/lib/src/utils/keys.dart @@ -0,0 +1,53 @@ +import '../models/control.dart'; + +abstract class ControlKey { + final Object value; + + const ControlKey(this.value) + : assert( + value is int || value is String || value is bool || value is double, + 'Key value value must be int, String, bool, or double'); + + @override + bool operator ==(Object other) => + identical(this, other) || + other.runtimeType == runtimeType && + other is ControlKey && + other.value == value; + + @override + int get hashCode => value.hashCode; + + @override + String toString() => value.toString(); +} + +class ControlScrollKey extends ControlKey { + const ControlScrollKey(super.value); +} + +class ControlValueKey extends ControlKey { + const ControlValueKey(super.value); +} + +ControlKey? parseKey(dynamic value) { + if (value == null) return null; + + if (value is Map) { + var type = value["_type"]; + if (type == "value") { + return ControlValueKey(value["value"]); + } else if (type == "scroll") { + return ControlScrollKey(value["value"]); + } + throw Exception("Unknown key type: $type"); + } else { + return ControlValueKey(value); + } +} + +extension KeysParsers on Control { + ControlKey? getKey(String propertyName) { + return parseKey(get(propertyName)); + } +} diff --git a/packages/flet/lib/src/utils/launch_url.dart b/packages/flet/lib/src/utils/launch_url.dart index 8ac11e5af..477bf53b6 100644 --- a/packages/flet/lib/src/utils/launch_url.dart +++ b/packages/flet/lib/src/utils/launch_url.dart @@ -1,7 +1,7 @@ import 'package:url_launcher/url_launcher.dart'; -import 'platform_utils_non_web.dart' - if (dart.library.js) "platform_utils_web.dart"; +import 'platform_utils_web.dart' + if (dart.library.io) "platform_utils_non_web.dart"; Future openWebBrowser(String url, {String? webWindowName, diff --git a/packages/flet/lib/src/utils/locale.dart b/packages/flet/lib/src/utils/locale.dart index 7d02acac2..03500136c 100644 --- a/packages/flet/lib/src/utils/locale.dart +++ b/packages/flet/lib/src/utils/locale.dart @@ -1,43 +1,40 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; import '../models/control.dart'; -Map? parseLocaleConfiguration( - Control control, String propName) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } +class LocaleConfiguration { + final List supportedLocales; + final Locale? locale; - final j1 = json.decode(v); - return localeConfigurationFromJSON(j1); + const LocaleConfiguration( + {required this.supportedLocales, required this.locale}); } -Map localeConfigurationFromJSON(dynamic json) { +LocaleConfiguration parseLocaleConfiguration(dynamic value) { List? supportedLocales; - var sl = json["supported_locales"]; - Locale? locale = json["current_locale"] != null - ? localeFromJSON(json["current_locale"]) - : null; - if (sl != null) { - supportedLocales = - sl.map((e) => localeFromJSON(e)).whereType().toList(); + Locale? locale; + + if (value != null) { + var sl = value["supported_locales"]; + if (sl != null) { + supportedLocales = + sl.map((e) => parseLocale(e)).whereType().toList(); + } + locale = parseLocale(value["current_locale"]); } - return { - "supportedLocales": supportedLocales != null && supportedLocales.isNotEmpty - ? supportedLocales - : [const Locale("en", "US")], // American locale as fallback - "locale": locale - }; + return LocaleConfiguration( + supportedLocales: supportedLocales != null && supportedLocales.isNotEmpty + ? supportedLocales + : [const Locale("en", "US")], + locale: locale); } -Locale localeFromJSON(dynamic json) { - var languageCode = json["language_code"]?.trim(); - var countryCode = json["country_code"]?.trim(); - var scriptCode = json["script_code"]?.trim(); +Locale? parseLocale(dynamic value, [Locale? defaultValue]) { + if (value == null) return defaultValue; + var languageCode = value["language_code"]?.trim(); + var countryCode = value["country_code"]?.trim(); + var scriptCode = value["script_code"]?.trim(); return Locale.fromSubtags( languageCode: (languageCode != null && languageCode.isNotEmpty) ? languageCode @@ -47,3 +44,14 @@ Locale localeFromJSON(dynamic json) { scriptCode: (scriptCode != null && scriptCode.isNotEmpty) ? scriptCode : null); } + +extension LocaleParsers on Control { + LocaleConfiguration getLocaleConfiguration(String propertyName, + [LocaleConfiguration? defaultValue]) { + return parseLocaleConfiguration(get(propertyName)); + } + + Locale? getLocale(String propertyName, [Locale? defaultValue]) { + return parseLocale(get(propertyName), defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/markdown.dart b/packages/flet/lib/src/utils/markdown.dart index bec42e62a..5e3716250 100644 --- a/packages/flet/lib/src/utils/markdown.dart +++ b/packages/flet/lib/src/utils/markdown.dart @@ -1,12 +1,9 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:flutter_highlight/theme_map.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:markdown/markdown.dart' as md; import '../models/control.dart'; -import '../models/page_args_model.dart'; import 'alignment.dart'; import 'box.dart'; import 'edge_insets.dart'; @@ -14,10 +11,8 @@ import 'numbers.dart'; import 'text.dart'; md.ExtensionSet? parseMarkdownExtensionSet(String? value, - [md.ExtensionSet? defValue]) { - if (value == null) { - return defValue; - } + [md.ExtensionSet? defaultValue]) { + if (value == null) return defaultValue; switch (value.toLowerCase()) { case "commonmark": return md.ExtensionSet.commonMark; @@ -26,23 +21,14 @@ md.ExtensionSet? parseMarkdownExtensionSet(String? value, case "githubflavored": return md.ExtensionSet.gitHubFlavored; default: - return defValue; + return defaultValue; } } -Map parseMarkdownCodeTheme( - Control control, - String propName, - ThemeData theme, -) { - final v = control.attrString(propName); - if (v == null) { - return {}; - } - dynamic j = json.decode(v); - if (j is String) { - return themeMap[j.toLowerCase()] ?? {}; - } else if (j is Map) { +Map parseMarkdownCodeTheme(dynamic value, ThemeData theme) { + if (value == null) return {}; + if (value is String) return themeMap[value.toLowerCase()] ?? {}; + if (value is Map) { String transformKey(String key) { switch (key) { case 'class_name': @@ -55,7 +41,7 @@ Map parseMarkdownCodeTheme( } final resultMap = - j.map((key, value) => MapEntry(key, textStyleFromJson(theme, value))); + value.map((key, value) => MapEntry(key, parseTextStyle(value, theme))); resultMap.removeWhere( (key, value) => value == null); // remove entries with null values return resultMap.map((key, value) => MapEntry(transformKey(key), value!)); @@ -63,116 +49,122 @@ Map parseMarkdownCodeTheme( return {}; } -MarkdownStyleSheet? parseMarkdownStyleSheet(Control control, String propName, - ThemeData theme, PageArgsModel? pageArgs) { - var v = control.attrString(propName); - if (v == null) { - return null; - } - dynamic j = json.decode(v); - return markdownStyleSheetFromJson(theme, j, pageArgs); -} - -MarkdownStyleSheet markdownStyleSheetFromJson( - ThemeData theme, Map j, PageArgsModel? pageArgs) { - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } - +MarkdownStyleSheet? parseMarkdownStyleSheet(dynamic value, BuildContext context, + [MarkdownStyleSheet? defaultValue]) { + if (value == null) return null; + var theme = Theme.of(context); return MarkdownStyleSheet.fromTheme(theme).copyWith( - a: parseTextStyle("a_text_style") ?? const TextStyle(color: Colors.blue), - p: parseTextStyle("p_text_style") ?? theme.textTheme.bodyMedium, - pPadding: edgeInsetsFromJson(j["p_padding"], EdgeInsets.zero)!, - code: parseTextStyle("code_text_style") ?? - theme.textTheme.bodyMedium!.copyWith(fontFamily: "monospace"), - h1: parseTextStyle("h1_text_style") ?? theme.textTheme.headlineSmall, - h1Padding: edgeInsetsFromJson(j["h1_padding"], EdgeInsets.zero)!, - h2: parseTextStyle("h2_text_style") ?? theme.textTheme.titleLarge, - h2Padding: edgeInsetsFromJson(j["h2_padding"], EdgeInsets.zero)!, - h3: parseTextStyle("h3_text_style") ?? theme.textTheme.titleMedium, - h3Padding: edgeInsetsFromJson(j["h3_padding"], EdgeInsets.zero)!, - h4: parseTextStyle("h4_text_style") ?? theme.textTheme.bodyLarge, - h4Padding: edgeInsetsFromJson(j["h4_padding"], EdgeInsets.zero)!, - h5: parseTextStyle("h5_text_style") ?? theme.textTheme.bodyLarge, - h5Padding: edgeInsetsFromJson(j["h5_padding"], EdgeInsets.zero)!, - h6: parseTextStyle("h6_text_style") ?? theme.textTheme.bodyLarge, - h6Padding: edgeInsetsFromJson(j["h6_padding"], EdgeInsets.zero)!, - em: parseTextStyle("em_text_style") ?? - const TextStyle(fontStyle: FontStyle.italic), - strong: parseTextStyle("strong_text_style") ?? - const TextStyle(fontWeight: FontWeight.bold), - del: parseTextStyle("del_text_style") ?? - const TextStyle(decoration: TextDecoration.lineThrough), - blockquote: - parseTextStyle("blockquote_text_style") ?? theme.textTheme.bodyMedium, - img: parseTextStyle("img_text_style") ?? theme.textTheme.bodyMedium, - checkbox: parseTextStyle("checkbox_text_style") ?? - theme.textTheme.bodyMedium!.copyWith( - color: theme.primaryColor, - ), - blockSpacing: parseDouble(j["block_spacing"], 8.0)!, - listIndent: parseDouble(j["list_indent"], 24.0)!, - listBullet: - parseTextStyle("list_bullet_text_style") ?? theme.textTheme.bodyMedium, - listBulletPadding: edgeInsetsFromJson( - j["list_bullet_padding"], const EdgeInsets.only(right: 4))!, - tableHead: parseTextStyle("table_head_text_style") ?? - const TextStyle(fontWeight: FontWeight.w600), - tableBody: - parseTextStyle("table_body_text_style") ?? theme.textTheme.bodyMedium, + a: parseTextStyle( + value["a_text_style"], theme, const TextStyle(color: Colors.blue))!, + p: parseTextStyle(value["p_text_style"], theme, theme.textTheme.bodyMedium), + pPadding: parsePadding(value["p_padding"], EdgeInsets.zero)!, + code: parseTextStyle(value["code_text_style"], theme, + theme.textTheme.bodyMedium!.copyWith(fontFamily: "monospace")), + h1: parseTextStyle(value["h1_text_style"], theme) ?? + theme.textTheme.headlineSmall, + h1Padding: parsePadding(value["h1_padding"], EdgeInsets.zero)!, + h2: parseTextStyle( + value["h2_text_style"], theme, theme.textTheme.titleLarge), + h2Padding: parsePadding(value["h2_padding"], EdgeInsets.zero)!, + h3: parseTextStyle( + value["h3_text_style"], theme, theme.textTheme.titleMedium), + h3Padding: parsePadding(value["h3_padding"], EdgeInsets.zero)!, + h4: parseTextStyle( + value["h4_text_style"], theme, theme.textTheme.bodyLarge), + h4Padding: parsePadding(value["h4_padding"], EdgeInsets.zero)!, + h5: parseTextStyle( + value["h5_text_style"], theme, theme.textTheme.bodyLarge), + h5Padding: parsePadding(value["h5_padding"], EdgeInsets.zero)!, + h6: parseTextStyle( + value["h6_text_style"], theme, theme.textTheme.bodyLarge), + h6Padding: parsePadding(value["h6_padding"], EdgeInsets.zero)!, + em: parseTextStyle(value["em_text_style"], theme, + const TextStyle(fontStyle: FontStyle.italic))!, + strong: parseTextStyle(value["strong_text_style"], theme, + const TextStyle(fontWeight: FontWeight.bold))!, + del: parseTextStyle(value["del_text_style"], theme, + const TextStyle(decoration: TextDecoration.lineThrough)), + blockquote: parseTextStyle( + value["blockquote_text_style"], theme, theme.textTheme.bodyMedium), + img: parseTextStyle( + value["img_text_style"], theme, theme.textTheme.bodyMedium), + checkbox: parseTextStyle(value["checkbox_text_style"], theme, + theme.textTheme.bodyMedium!.copyWith(color: theme.primaryColor)), + blockSpacing: parseDouble(value["block_spacing"], 8.0)!, + listIndent: parseDouble(value["list_indent"], 24.0)!, + listBullet: parseTextStyle( + value["list_bullet_text_style"], theme, theme.textTheme.bodyMedium), + listBulletPadding: parsePadding( + value["list_bullet_padding"], const EdgeInsets.only(right: 4))!, + tableHead: parseTextStyle(value["table_head_text_style"], theme, + const TextStyle(fontWeight: FontWeight.w600))!, + tableBody: parseTextStyle( + value["table_body_text_style"], theme, theme.textTheme.bodyMedium), tableHeadAlign: - parseTextAlign(j["table_head_text_align"], TextAlign.center)!, - tablePadding: edgeInsetsFromJson( - j["table_padding"], const EdgeInsets.only(bottom: 4.0))!, - tableBorder: TableBorder.all( - color: theme.dividerColor, - ), + parseTextAlign(value["table_head_text_align"], TextAlign.center)!, + tablePadding: parsePadding( + value["table_padding"], const EdgeInsets.only(bottom: 4.0))!, + tableBorder: TableBorder.all(color: theme.dividerColor), tableColumnWidth: const FlexColumnWidth(), - tableCellsPadding: edgeInsetsFromJson( - j["table_cells_padding"], const EdgeInsets.fromLTRB(16, 8, 16, 8))!, - tableCellsDecoration: - boxDecorationFromJSON(theme, j["table_cells_decoration"], pageArgs) ?? - const BoxDecoration(), - blockquotePadding: edgeInsetsFromJson(j["blockquote_padding"]) ?? - const EdgeInsets.all(8.0), - blockquoteDecoration: - boxDecorationFromJSON(theme, j["blockquote_decoration"], pageArgs) ?? - BoxDecoration( - color: Colors.blue.shade100, - borderRadius: BorderRadius.circular(2.0), - ), + tableCellsPadding: parsePadding( + value["table_cells_padding"], const EdgeInsets.fromLTRB(16, 8, 16, 8))!, + tableCellsDecoration: parseBoxDecoration( + value["table_cells_decoration"], context, const BoxDecoration()), + blockquotePadding: + parsePadding(value["blockquote_padding"], const EdgeInsets.all(8.0))!, + blockquoteDecoration: parseBoxDecoration( + value["blockquote_decoration"], + context, + BoxDecoration( + color: Colors.blue.shade100, + borderRadius: BorderRadius.circular(2.0)))!, codeblockPadding: - edgeInsetsFromJson(j["codeblock_padding"], const EdgeInsets.all(8.0))!, - codeblockDecoration: - boxDecorationFromJSON(theme, j["codeblock_decoration"], pageArgs) ?? - BoxDecoration( - color: theme.cardTheme.color ?? theme.cardColor, - borderRadius: BorderRadius.circular(2.0), - ), - horizontalRuleDecoration: boxDecorationFromJSON( - theme, j["horizontal_rule_decoration"], pageArgs) ?? + parsePadding(value["codeblock_padding"], const EdgeInsets.all(8.0))!, + codeblockDecoration: parseBoxDecoration( + value["codeblock_decoration"], + context, BoxDecoration( - border: Border( - top: BorderSide( - width: 5.0, - color: theme.dividerColor, - ), - ), - ), + color: theme.cardTheme.color ?? theme.cardColor, + borderRadius: BorderRadius.circular(2.0)))!, + horizontalRuleDecoration: parseBoxDecoration( + value["horizontal_rule_decoration"], + context, + BoxDecoration( + border: Border( + top: BorderSide(width: 5.0, color: theme.dividerColor))))!, blockquoteAlign: - parseWrapAlignment(j["blockquote_alignment"], WrapAlignment.start)!, + parseWrapAlignment(value["blockquote_alignment"], WrapAlignment.start)!, codeblockAlign: - parseWrapAlignment(j["codeblock_alignment"], WrapAlignment.start)!, - h1Align: parseWrapAlignment(j["h1_alignment"], WrapAlignment.start)!, - h2Align: parseWrapAlignment(j["h2_alignment"], WrapAlignment.start)!, - h3Align: parseWrapAlignment(j["h3_alignment"], WrapAlignment.start)!, - h4Align: parseWrapAlignment(j["h4_alignment"], WrapAlignment.start)!, - h5Align: parseWrapAlignment(j["h5_alignment"], WrapAlignment.start)!, - h6Align: parseWrapAlignment(j["h6_alignment"], WrapAlignment.start)!, - textAlign: parseWrapAlignment(j["text_alignment"], WrapAlignment.start)!, - orderedListAlign: - parseWrapAlignment(j["ordered_list_alignment"], WrapAlignment.start)!, - unorderedListAlign: - parseWrapAlignment(j["unordered_list_alignment"], WrapAlignment.start)!, + parseWrapAlignment(value["codeblock_alignment"], WrapAlignment.start)!, + h1Align: parseWrapAlignment(value["h1_alignment"], WrapAlignment.start)!, + h2Align: parseWrapAlignment(value["h2_alignment"], WrapAlignment.start)!, + h3Align: parseWrapAlignment(value["h3_alignment"], WrapAlignment.start)!, + h4Align: parseWrapAlignment(value["h4_alignment"], WrapAlignment.start)!, + h5Align: parseWrapAlignment(value["h5_alignment"], WrapAlignment.start)!, + h6Align: parseWrapAlignment(value["h6_alignment"], WrapAlignment.start)!, + textAlign: + parseWrapAlignment(value["text_alignment"], WrapAlignment.start)!, + orderedListAlign: parseWrapAlignment( + value["ordered_list_alignment"], WrapAlignment.start)!, + unorderedListAlign: parseWrapAlignment( + value["unordered_list_alignment"], WrapAlignment.start)!, ); } + +extension MarkdownParsers on Control { + Map getMarkdownCodeTheme( + String propertyName, ThemeData theme) { + return parseMarkdownCodeTheme(get(propertyName), theme); + } + + md.ExtensionSet? getMarkdownExtensionSet(String propertyName, + [md.ExtensionSet? defaultValue]) { + return parseMarkdownExtensionSet(get(propertyName), defaultValue); + } + + MarkdownStyleSheet? getMarkdownStyleSheet( + String propertyName, BuildContext context, + [MarkdownStyleSheet? defaultValue]) { + return parseMarkdownStyleSheet(get(propertyName), context, defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/material_state.dart b/packages/flet/lib/src/utils/material_state.dart index 65421197c..7e0db7c72 100644 --- a/packages/flet/lib/src/utils/material_state.dart +++ b/packages/flet/lib/src/utils/material_state.dart @@ -1,14 +1,23 @@ import 'dart:collection'; + import 'package:flutter/material.dart'; +import '../models/control.dart'; + WidgetStateProperty? getWidgetStateProperty( - dynamic jsonDictValue, T Function(dynamic) converterFromJson, + dynamic value, T Function(dynamic) converterFromJson, [T? defaultValue]) { - if (jsonDictValue == null) { - return null; + if (value == null) return null; + var j = value; + if (j is! Map) { + j = {"default": j}; + } + if (j.containsKey("")) { + j["default"] = j.remove(""); } - var j = jsonDictValue; - if (j is! Map) { + if (!j.keys.every( + (k) => k == "default" || WidgetState.values.any((v) => v.name == k))) { + // wrap into another dict j = {"default": j}; } return WidgetStateFromJSON(j, converterFromJson, defaultValue); @@ -18,7 +27,7 @@ class WidgetStateFromJSON extends WidgetStateProperty { late final LinkedHashMap _states; late final T? _defaultValue; - WidgetStateFromJSON(Map? jsonDictValue, + WidgetStateFromJSON(Map? jsonDictValue, T Function(dynamic) converterFromJson, T? defaultValue) { _defaultValue = defaultValue; @@ -48,3 +57,12 @@ class WidgetStateFromJSON extends WidgetStateProperty { return _states["default"] ?? _defaultValue; } } + +extension WidgetStatePropertyParsers on Control { + WidgetStateProperty? getWidgetStateProperty( + String propertyName, T Function(dynamic) converterFromJson, + [T? defaultValue]) { + return getWidgetStateProperty( + get(propertyName), converterFromJson, defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/menu.dart b/packages/flet/lib/src/utils/menu.dart index 8760f5eb2..47c281f19 100644 --- a/packages/flet/lib/src/utils/menu.dart +++ b/packages/flet/lib/src/utils/menu.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; import '../models/control.dart'; @@ -11,7 +9,7 @@ import 'material_state.dart'; import 'mouse.dart'; import 'numbers.dart'; -MenuStyle? parseMenuStyle(ThemeData theme, Control control, String propName, +MenuStyle? parseMenuStyle(dynamic value, ThemeData theme, {Color? defaultBackgroundColor, Color? defaultShadowColor, Color? defaultSurfaceTintColor, @@ -20,59 +18,56 @@ MenuStyle? parseMenuStyle(ThemeData theme, Control control, String propName, MouseCursor? defaultMouseCursor, EdgeInsets? defaultPadding, BorderSide? defaultBorderSide, - OutlinedBorder? defaultShape}) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - - final j1 = json.decode(v); - return menuStyleFromJSON( - theme, - j1, - defaultBackgroundColor, - defaultShadowColor, - defaultSurfaceTintColor, - defaultElevation, - defaultAlignment, - defaultMouseCursor, - defaultPadding, - defaultBorderSide, - defaultShape); -} + OutlinedBorder? defaultShape, + MenuStyle? defaultValue}) { + if (value == null) return defaultValue; -MenuStyle? menuStyleFromJSON(ThemeData theme, Map? json, - [Color? defaultBackgroundColor, - Color? defaultShadowColor, - Color? defaultSurfaceTintColor, - double? defaultElevation, - Alignment? defaultAlignment, - MouseCursor? defaultMouseCursor, - EdgeInsets? defaultPadding, - BorderSide? defaultBorderSide, - OutlinedBorder? defaultShape]) { - if (json == null) { - return null; - } return MenuStyle( - alignment: alignmentFromJson(json["alignment"], defaultAlignment)!, - backgroundColor: getWidgetStateProperty(json["bgcolor"], - (jv) => parseColor(theme, jv as String), defaultBackgroundColor), - shadowColor: getWidgetStateProperty(json["shadow_color"], - (jv) => parseColor(theme, jv as String), defaultShadowColor), - surfaceTintColor: getWidgetStateProperty(json["surface_tint_color"], - (jv) => parseColor(theme, jv as String), defaultSurfaceTintColor), - elevation: getWidgetStateProperty( - json["elevation"], (jv) => parseDouble(jv, 0)!, defaultElevation), + alignment: parseAlignment(value["alignment"], defaultAlignment)!, + backgroundColor: parseWidgetStateColor(value["bgcolor"], theme, + defaultColor: defaultBackgroundColor), + shadowColor: parseWidgetStateColor(value["shadow_color"], theme, + defaultColor: defaultShadowColor), + surfaceTintColor: parseWidgetStateColor(value["surface_tint_color"], theme, + defaultColor: defaultSurfaceTintColor), + elevation: parseWidgetStateDouble(value["elevation"], + defaultDouble: defaultElevation), padding: getWidgetStateProperty( - json["padding"], (jv) => edgeInsetsFromJson(jv), defaultPadding), + value["padding"], (jv) => parseEdgeInsets(jv), defaultPadding), side: getWidgetStateProperty( - json["side"], - (jv) => borderSideFromJSON(theme, jv, theme.colorScheme.outline), + value["side"], + (jv) => parseBorderSide(jv, theme, + defaultSideColor: theme.colorScheme.outline), defaultBorderSide), - shape: getWidgetStateProperty( - json["shape"], (jv) => outlinedBorderFromJSON(jv), defaultShape), - mouseCursor: getWidgetStateProperty(json["mouse_cursor"], - (jv) => parseMouseCursor(jv as String), defaultMouseCursor), + shape: parseWidgetStateOutlinedBorder(value["shape"], theme, + defaultOutlinedBorder: defaultShape), + mouseCursor: parseWidgetStateMouseCursor(value["mouse_cursor"], + defaultMouseCursor: defaultMouseCursor), ); } + +extension MenuParsers on Control { + MenuStyle? getMenuStyle(String propertyName, ThemeData theme, + {Color? defaultBackgroundColor, + Color? defaultShadowColor, + Color? defaultSurfaceTintColor, + double? defaultElevation, + Alignment? defaultAlignment, + MouseCursor? defaultMouseCursor, + EdgeInsets? defaultPadding, + BorderSide? defaultBorderSide, + OutlinedBorder? defaultShape, + MenuStyle? defaultValue}) { + return parseMenuStyle(get(propertyName), theme, + defaultBackgroundColor: defaultBackgroundColor, + defaultShadowColor: defaultShadowColor, + defaultSurfaceTintColor: defaultSurfaceTintColor, + defaultElevation: defaultElevation, + defaultAlignment: defaultAlignment, + defaultMouseCursor: defaultMouseCursor, + defaultPadding: defaultPadding, + defaultBorderSide: defaultBorderSide, + defaultShape: defaultShape, + defaultValue: defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/misc.dart b/packages/flet/lib/src/utils/misc.dart new file mode 100644 index 000000000..e5d71bf7a --- /dev/null +++ b/packages/flet/lib/src/utils/misc.dart @@ -0,0 +1,281 @@ +import 'dart:ui'; + +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../models/control.dart'; + +Clip? parseClip(String? value, [Clip? defaultValue]) { + if (value == null) return defaultValue; + return Clip.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +Orientation? parseOrientation(String? value, [Orientation? defaultValue]) { + if (value == null) return defaultValue; + return Orientation.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +StrokeCap? parseStrokeCap(String? value, [StrokeCap? defaultValue]) { + if (value == null) return defaultValue; + return StrokeCap.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +StrokeJoin? parseStrokeJoin(String? value, [StrokeJoin? defaultValue]) { + if (value == null) return defaultValue; + return StrokeJoin.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +BoxShape? parseBoxShape(String? value, [BoxShape? defaultValue]) { + if (value == null) return defaultValue; + return BoxShape.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +NotchedShape? parseNotchedShape(String? value, [NotchedShape? defaultValue]) { + if (value == null) { + return defaultValue; + } else if (value == "circular") { + return const CircularNotchedRectangle(); + } else if (value == "auto") { + return const AutomaticNotchedShape(ContinuousRectangleBorder()); + } else { + return defaultValue; + } +} + +SliderInteraction? parseSliderInteraction(String? value, + [SliderInteraction? defaultValue]) { + if (value == null) return defaultValue; + return SliderInteraction.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +SnackBarBehavior? parseSnackBarBehavior(String? value, + [SnackBarBehavior? defaultValue]) { + if (value == null) return defaultValue; + return SnackBarBehavior.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +StackFit? parseStackFit(String? value, [StackFit? defaultValue]) { + if (value == null) return defaultValue; + return StackFit.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +enum CardVariant { elevated, filled, outlined } + +CardVariant? parseCardVariant(String? value, [CardVariant? defaultValue]) { + if (value == null) return defaultValue; + return CardVariant.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +enum ScrollMode { none, auto, adaptive, always, hidden } + +ScrollMode? parseScrollMode(String? value, + [ScrollMode? defaultValue = ScrollMode.none]) { + if (value == null) return defaultValue; + return ScrollMode.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +enum LabelPosition { right, left } + +LabelPosition? parseLabelPosition(String? value, + [LabelPosition? defaultValue]) { + if (value == null) return defaultValue; + return LabelPosition.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +ListTileControlAffinity? parseListTileControlAffinity(String? value, + [ListTileControlAffinity? defaultValue]) { + if (value == null) return defaultValue; + return ListTileControlAffinity.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +ListTileStyle? parseListTileStyle(String? value, + [ListTileStyle? defaultValue]) { + if (value == null) return defaultValue; + return ListTileStyle.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +NavigationDestinationLabelBehavior? parseNavigationDestinationLabelBehavior( + String? value, + [NavigationDestinationLabelBehavior? defaultValue]) { + if (value == null) return defaultValue; + return NavigationDestinationLabelBehavior.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +PopupMenuPosition? parsePopupMenuPosition(String? value, + [PopupMenuPosition? defaultValue]) { + if (value == null) return defaultValue; + return PopupMenuPosition.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +Assertiveness? parseAssertiveness(String? value, + [Assertiveness? defaultValue]) { + if (value == null) return defaultValue; + return Assertiveness.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +ListTileTitleAlignment? parseListTileTitleAlignment(String? value, + [ListTileTitleAlignment? defaultValue]) { + if (value == null) return defaultValue; + return ListTileTitleAlignment.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +Axis? parseAxis(String? value, [Axis? defaultValue]) { + if (value == null) return defaultValue; + return Axis.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +PointerDeviceKind? parsePointerDeviceKind(String? value, + [PointerDeviceKind? defaultValue]) { + if (value == null) return defaultValue; + return PointerDeviceKind.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +NavigationRailLabelType? parseNavigationRailLabelType(String? value, + [NavigationRailLabelType? defaultValue]) { + if (value == null) return defaultValue; + return NavigationRailLabelType.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +extension MiscParsers on Control { + Clip? getClipBehavior(String propertyName, [Clip? defaultValue]) { + return parseClip(get(propertyName), defaultValue); + } + + Orientation? getOrientation(String propertyName, + [Orientation? defaultValue]) { + return parseOrientation(get(propertyName), defaultValue); + } + + StrokeCap? getStrokeCap(String propertyName, [StrokeCap? defaultValue]) { + return parseStrokeCap(get(propertyName), defaultValue); + } + + StrokeJoin? getStrokeJoin(String propertyName, [StrokeJoin? defaultValue]) { + return parseStrokeJoin(get(propertyName), defaultValue); + } + + BoxShape? getBoxShape(String propertyName, [BoxShape? defaultValue]) { + return parseBoxShape(get(propertyName), defaultValue); + } + + NotchedShape? getNotchedShape(String propertyName, + [NotchedShape? defaultValue]) { + return parseNotchedShape(get(propertyName), defaultValue); + } + + SliderInteraction? getSliderInteraction(String propertyName, + [SliderInteraction? defaultValue]) { + return parseSliderInteraction(get(propertyName), defaultValue); + } + + SnackBarBehavior? getSnackBarBehavior(String propertyName, + [SnackBarBehavior? defaultValue]) { + return parseSnackBarBehavior(get(propertyName), defaultValue); + } + + StackFit? getStackFit(String propertyName, [StackFit? defaultValue]) { + return parseStackFit(get(propertyName), defaultValue); + } + + CardVariant? getCardVariant(String propertyName, + [CardVariant? defaultValue]) { + return parseCardVariant(get(propertyName), defaultValue); + } + + ScrollMode? getScrollMode(String propertyName, + [ScrollMode? defaultValue = ScrollMode.none]) { + return parseScrollMode(get(propertyName), defaultValue); + } + + LabelPosition? getLabelPosition(String propertyName, + [LabelPosition? defaultValue]) { + return parseLabelPosition(get(propertyName), defaultValue); + } + + ListTileControlAffinity? getListTileControlAffinity(String propertyName, + [ListTileControlAffinity? defaultValue]) { + return parseListTileControlAffinity(get(propertyName), defaultValue); + } + + ListTileStyle? getListTileStyle(String propertyName, + [ListTileStyle? defaultValue]) { + return parseListTileStyle(get(propertyName), defaultValue); + } + + NavigationDestinationLabelBehavior? getNavigationDestinationLabelBehavior( + String propertyName, + [NavigationDestinationLabelBehavior? defaultValue]) { + return parseNavigationDestinationLabelBehavior( + get(propertyName), defaultValue); + } + + PopupMenuPosition? getPopupMenuPosition(String propertyName, + [PopupMenuPosition? defaultValue]) { + return parsePopupMenuPosition(get(propertyName), defaultValue); + } + + Assertiveness? getAssertiveness(String propertyName, + [Assertiveness? defaultValue]) { + return parseAssertiveness(get(propertyName), defaultValue); + } + + ListTileTitleAlignment? getListTileTitleAlignment(String propertyName, + [ListTileTitleAlignment? defaultValue]) { + return parseListTileTitleAlignment(get(propertyName), defaultValue); + } + + Axis? getAxis(String propertyName, [Axis? defaultValue]) { + return parseAxis(get(propertyName), defaultValue); + } + + PointerDeviceKind? getPointerDeviceKind(String propertyName, + [PointerDeviceKind? defaultValue]) { + return parsePointerDeviceKind(get(propertyName), defaultValue); + } + + NavigationRailLabelType? getNavigationRailLabelType(String propertyName, + [NavigationRailLabelType? defaultValue]) { + return parseNavigationRailLabelType(get(propertyName), defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/mouse.dart b/packages/flet/lib/src/utils/mouse.dart index 73fc449f2..e867b822c 100644 --- a/packages/flet/lib/src/utils/mouse.dart +++ b/packages/flet/lib/src/utils/mouse.dart @@ -1,98 +1,71 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; import '../models/control.dart'; import 'material_state.dart'; -MouseCursor? parseMouseCursor(String? cursor, - [MouseCursor? defaultMouseCursor]) { - switch (cursor?.toLowerCase()) { - case "alias": - return SystemMouseCursors.alias; - case "allscroll": - return SystemMouseCursors.allScroll; - case "basic": - return SystemMouseCursors.basic; - case "cell": - return SystemMouseCursors.cell; - case "click": - return SystemMouseCursors.click; - case "contextmenu": - return SystemMouseCursors.contextMenu; - case "copy": - return SystemMouseCursors.copy; - case "disappearing": - return SystemMouseCursors.disappearing; - case "forbidden": - return SystemMouseCursors.forbidden; - case "grab": - return SystemMouseCursors.grab; - case "grabbing": - return SystemMouseCursors.grabbing; - case "help": - return SystemMouseCursors.help; - case "move": - return SystemMouseCursors.move; - case "nodrop": - return SystemMouseCursors.noDrop; - case "none": - return SystemMouseCursors.none; - case "precise": - return SystemMouseCursors.precise; - case "progress": - return SystemMouseCursors.progress; - case "resizecolumn": - return SystemMouseCursors.resizeColumn; - case "resizedown": - return SystemMouseCursors.resizeDown; - case "resizedownleft": - return SystemMouseCursors.resizeDownLeft; - case "resizedownright": - return SystemMouseCursors.resizeDownRight; - case "resizeleft": - return SystemMouseCursors.resizeLeft; - case "resizeleftright": - return SystemMouseCursors.resizeLeftRight; - case "resizeright": - return SystemMouseCursors.resizeRight; - case "resizerow": - return SystemMouseCursors.resizeRow; - case "resizeup": - return SystemMouseCursors.resizeUp; - case "resizeupdown": - return SystemMouseCursors.resizeUpDown; - case "resizeupleft": - return SystemMouseCursors.resizeUpLeft; - case "resizeupleftdownright": - return SystemMouseCursors.resizeUpLeftDownRight; - case "resizeupright": - return SystemMouseCursors.resizeUpRight; - case "resizeuprightdownleft": - return SystemMouseCursors.resizeUpRightDownLeft; - case "text": - return SystemMouseCursors.text; - case "verticaltext": - return SystemMouseCursors.verticalText; - case "wait": - return SystemMouseCursors.wait; - case "zoomin": - return SystemMouseCursors.zoomIn; - case "zoomout": - return SystemMouseCursors.zoomOut; - default: - return defaultMouseCursor; - } +MouseCursor? parseMouseCursor(String? cursor, [MouseCursor? defaultValue]) { + const cursorMap = { + "alias": SystemMouseCursors.alias, + "allscroll": SystemMouseCursors.allScroll, + "basic": SystemMouseCursors.basic, + "cell": SystemMouseCursors.cell, + "click": SystemMouseCursors.click, + "contextmenu": SystemMouseCursors.contextMenu, + "copy": SystemMouseCursors.copy, + "disappearing": SystemMouseCursors.disappearing, + "forbidden": SystemMouseCursors.forbidden, + "grab": SystemMouseCursors.grab, + "grabbing": SystemMouseCursors.grabbing, + "help": SystemMouseCursors.help, + "move": SystemMouseCursors.move, + "nodrop": SystemMouseCursors.noDrop, + "none": SystemMouseCursors.none, + "precise": SystemMouseCursors.precise, + "progress": SystemMouseCursors.progress, + "resizecolumn": SystemMouseCursors.resizeColumn, + "resizedown": SystemMouseCursors.resizeDown, + "resizedownleft": SystemMouseCursors.resizeDownLeft, + "resizedownright": SystemMouseCursors.resizeDownRight, + "resizeleft": SystemMouseCursors.resizeLeft, + "resizeleftright": SystemMouseCursors.resizeLeftRight, + "resizeright": SystemMouseCursors.resizeRight, + "resizerow": SystemMouseCursors.resizeRow, + "resizeup": SystemMouseCursors.resizeUp, + "resizeupdown": SystemMouseCursors.resizeUpDown, + "resizeupleft": SystemMouseCursors.resizeUpLeft, + "resizeupleftdownright": SystemMouseCursors.resizeUpLeftDownRight, + "resizeupright": SystemMouseCursors.resizeUpRight, + "resizeuprightdownleft": SystemMouseCursors.resizeUpRightDownLeft, + "text": SystemMouseCursors.text, + "verticaltext": SystemMouseCursors.verticalText, + "wait": SystemMouseCursors.wait, + "zoomin": SystemMouseCursors.zoomIn, + "zoomout": SystemMouseCursors.zoomOut, + }; + + return cursorMap[cursor?.toLowerCase() ?? ""] ?? defaultValue; } -WidgetStateProperty? parseWidgetStateMouseCursor( - Control control, String propName, - [MouseCursor? defaultValue]) { - var v = control.attrString(propName); - if (v == null) { - return null; - } +WidgetStateProperty? parseWidgetStateMouseCursor(dynamic value, + {MouseCursor? defaultMouseCursor, + WidgetStateProperty? defaultValue}) { + if (value == null) return defaultValue; return getWidgetStateProperty( - jsonDecode(v), (jv) => parseMouseCursor(jv as String), defaultValue); + value, (jv) => parseMouseCursor(jv as String), defaultMouseCursor); +} + +extension MouseParsers on Control { + MouseCursor? getMouseCursor(String propertyName, + [MouseCursor? defaultValue]) { + return parseMouseCursor(get(propertyName), defaultValue); + } + + WidgetStateProperty? getWidgetStateMouseCursor( + String propertyName, + {MouseCursor? defaultMouseCursor, + WidgetStateProperty? defaultValue}) { + return parseWidgetStateMouseCursor(get(propertyName), + defaultMouseCursor: defaultMouseCursor, defaultValue: defaultValue); + } } diff --git a/packages/flet/lib/src/utils/numbers.dart b/packages/flet/lib/src/utils/numbers.dart index bb3e142b1..9d6c73150 100644 --- a/packages/flet/lib/src/utils/numbers.dart +++ b/packages/flet/lib/src/utils/numbers.dart @@ -1,70 +1,94 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; import '../models/control.dart'; import 'material_state.dart'; -double? parseDouble(dynamic v, [double? defValue]) { - if (v is double) { - return v; - } else if (v is String && v.toLowerCase() == "inf") { +double? parseDouble(dynamic value, [double? defaultValue]) { + if (value is double) { + return value; + } else if (value is String && value.toLowerCase() == "inf") { return double.infinity; - } else if (v == null) { - return defValue; + } else if (value == null) { + return defaultValue; + } else if (value is int) { + return value.toDouble(); } else { - return double.tryParse(v.toString()) ?? defValue; + return double.tryParse(value.toString()) ?? defaultValue; } } -WidgetStateProperty? parseWidgetStateDouble( - Control control, String propName, - [double? defaultValue]) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } +WidgetStateProperty? parseWidgetStateDouble(dynamic value, + {double? defaultDouble, WidgetStateProperty? defaultValue}) { + if (value == null) return defaultValue; return getWidgetStateProperty( - jsonDecode(v), (jv) => parseDouble(jv), defaultValue); + value, (jv) => parseDouble(jv), defaultDouble); } -int? parseInt(dynamic v, [int? defValue]) { - if (v is int) { - return v; - } else if (v == null) { - return defValue; +int? parseInt(dynamic value, [int? defaultValue]) { + if (value is int) { + return value; + } else if (value == null) { + return defaultValue; } else { - return int.tryParse(v.toString()) ?? defValue; + return int.tryParse(value.toString()) ?? defaultValue; } } -WidgetStateProperty? parseWidgetStateInt(Control control, String propName, - [int? defaultValue]) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - return getWidgetStateProperty( - jsonDecode(v), (jv) => parseInt(jv), defaultValue); +WidgetStateProperty? parseWidgetStateInt(dynamic value, + {int? defaultInt, WidgetStateProperty? defaultValue}) { + if (value == null) return defaultValue; + return getWidgetStateProperty(value, (jv) => parseInt(jv), defaultInt); } -bool? parseBool(dynamic v, [bool? defValue]) { - if (v is bool) { - return v; - } else if (v == null) { - return defValue; +bool? parseBool(dynamic value, [bool? defaultValue]) { + if (value is bool) { + return value; + } else if (value == null) { + return defaultValue; } else { - return "true" == v.toString().toLowerCase(); + return "true" == value.toString().toLowerCase(); } } -WidgetStateProperty? parseWidgetStateBool( - Control control, String propName, - [bool? defaultValue]) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } +WidgetStateProperty? parseWidgetStateBool(dynamic value, + {bool? defaultBool, WidgetStateProperty? defaultValue}) { + if (value == null) return defaultValue; return getWidgetStateProperty( - jsonDecode(v), (jv) => parseBool(jv), defaultValue); + value, (jv) => parseBool(jv), defaultBool); +} + +extension LiteralParsers on Control { + bool? getBool(String propertyName, [bool? defaultValue]) { + return get(propertyName, defaultValue); + } + + String? getString(String propertyName, [String? defaultValue]) { + return get(propertyName, defaultValue); + } + + int? getInt(String propertyName, [int? defaultValue]) { + return get(propertyName, defaultValue); + } + + double? getDouble(String propertyName, [double? defaultValue]) { + return get(propertyName, defaultValue); + } + + WidgetStateProperty? getWidgetStateDouble(String propertyName, + {double? defaultDouble, WidgetStateProperty? defaultValue}) { + return parseWidgetStateDouble(get(propertyName), + defaultDouble: defaultDouble, defaultValue: defaultValue); + } + + WidgetStateProperty? getWidgetStateInt(String propertyName, + {int? defaultInt, WidgetStateProperty? defaultValue}) { + return parseWidgetStateInt(get(propertyName), + defaultInt: defaultInt, defaultValue: defaultValue); + } + + WidgetStateProperty? getWidgetStateBool(String propertyName, + {bool? defaultBool, WidgetStateProperty? defaultValue}) { + return parseWidgetStateBool(get(propertyName), + defaultBool: defaultBool, defaultValue: defaultValue); + } } diff --git a/packages/flet/lib/src/utils/others.dart b/packages/flet/lib/src/utils/others.dart deleted file mode 100644 index 6727e01be..000000000 --- a/packages/flet/lib/src/utils/others.dart +++ /dev/null @@ -1,288 +0,0 @@ -import 'dart:convert'; -import 'dart:ui'; - -import 'package:collection/collection.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; - -import '../models/control.dart'; -import 'numbers.dart'; - -Clip? parseClip(String? value, [Clip? defaultValue]) { - if (value == null) { - return defaultValue; - } - return Clip.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defaultValue; -} - -Orientation? parseOrientation(String? value, - [Orientation? defaultOrientation]) { - if (value == null) { - return defaultOrientation; - } - return Orientation.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defaultOrientation; -} - -StrokeCap? parseStrokeCap(String? value, [StrokeCap? defValue]) { - if (value == null) { - return defValue; - } - return StrokeCap.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -StrokeJoin? parseStrokeJoin(String? value, [StrokeJoin? defValue]) { - if (value == null) { - return defValue; - } - return StrokeJoin.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -BoxShape? parseBoxShape(String? value, [BoxShape? defValue]) { - if (value == null) { - return defValue; - } - return BoxShape.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -NotchedShape? parseNotchedShape(String? value, [NotchedShape? defValue]) { - if (value == null) { - return defValue; - } else if (value == "circular") { - return const CircularNotchedRectangle(); - } else if (value == "auto") { - return const AutomaticNotchedShape(ContinuousRectangleBorder()); - } else { - return defValue; - } -} - -SliderInteraction? parseSliderInteraction(String? value, - [SliderInteraction? defValue]) { - if (value == null) { - return defValue; - } - return SliderInteraction.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -Size? parseSize(Control control, String propName, [Size? defValue]) { - var v = control.attrString(propName, null); - if (v == null) { - return defValue; - } - - final j1 = json.decode(v); - return sizeFromJson(j1, defValue); -} - -Size? sizeFromJson(Map? json, [Size? defValue]) { - if (json == null) return defValue; - - final width = parseDouble(json['width']); - final height = parseDouble(json['height']); - - if (width == null || height == null) return defValue; - - return Size(width, height); -} - -SnackBarBehavior? parseSnackBarBehavior(String? value, - [SnackBarBehavior? defValue]) { - if (value == null) { - return defValue; - } - return SnackBarBehavior.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -StackFit? parseStackFit(String? value, [StackFit? defValue]) { - if (value == null) { - return defValue; - } - return StackFit.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -DatePickerMode? parseDatePickerMode(String? value, [DatePickerMode? defValue]) { - if (value == null) { - return defValue; - } - return DatePickerMode.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -DatePickerEntryMode? parseDatePickerEntryMode(String? value, - [DatePickerEntryMode? defValue]) { - if (value == null) { - return defValue; - } - return DatePickerEntryMode.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -enum CardVariant { elevated, filled, outlined } - -CardVariant? parseCardVariant(String? value, [CardVariant? defValue]) { - if (value == null) { - return defValue; - } - return CardVariant.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -enum ScrollMode { none, auto, adaptive, always, hidden } - -ScrollMode? parseScrollMode(String? value, [ScrollMode? defValue]) { - if (value == null) { - return defValue; - } - return ScrollMode.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -enum LabelPosition { right, left } - -LabelPosition? parseLabelPosition(String? value, [LabelPosition? defValue]) { - if (value == null) { - return defValue; - } - return LabelPosition.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -CupertinoTimerPickerMode? parseCupertinoTimerPickerMode(String? value, - [CupertinoTimerPickerMode? defValue]) { - if (value == null) { - return defValue; - } - return CupertinoTimerPickerMode.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -ListTileControlAffinity? parseListTileControlAffinity(String? value, - [ListTileControlAffinity? defValue]) { - if (value == null) { - return defValue; - } - return ListTileControlAffinity.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -ListTileStyle? parseListTileStyle(String? value, [ListTileStyle? defValue]) { - if (value == null) { - return defValue; - } - return ListTileStyle.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -NavigationDestinationLabelBehavior? parseNavigationDestinationLabelBehavior( - String? value, - [NavigationDestinationLabelBehavior? defValue]) { - if (value == null) { - return defValue; - } - return NavigationDestinationLabelBehavior.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -PopupMenuPosition? parsePopupMenuPosition(String? value, - [PopupMenuPosition? defValue]) { - if (value == null) { - return defValue; - } - return PopupMenuPosition.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -Assertiveness? parseAssertiveness(String? value, [Assertiveness? defValue]) { - if (value == null) { - return defValue; - } - return Assertiveness.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -DatePickerDateOrder? parseDatePickerDateOrder(String? value, - [DatePickerDateOrder? defValue]) { - if (value == null) { - return defValue; - } - return DatePickerDateOrder.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -CupertinoDatePickerMode? parseCupertinoDatePickerMode(String? value, - [CupertinoDatePickerMode? defValue]) { - if (value == null) { - return defValue; - } - return CupertinoDatePickerMode.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -ListTileTitleAlignment? parseListTileTitleAlignment(String? value, - [ListTileTitleAlignment? defValue]) { - if (value == null) { - return defValue; - } - return ListTileTitleAlignment.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -TimePickerEntryMode? parseTimePickerEntryMode(String? value, - [TimePickerEntryMode? defValue]) { - if (value == null) { - return defValue; - } - return TimePickerEntryMode.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -Axis? parseAxis(String? value, [Axis? defValue]) { - if (value == null) { - return defValue; - } - return Axis.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} - -PointerDeviceKind? parsePointerDeviceKind(String? value, - [PointerDeviceKind? defValue]) { - if (value == null) { - return defValue; - } - return PointerDeviceKind.values.firstWhereOrNull( - (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; -} diff --git a/packages/flet/lib/src/utils/overlay_style.dart b/packages/flet/lib/src/utils/overlay_style.dart index b0ec38f0d..be4c063b4 100644 --- a/packages/flet/lib/src/utils/overlay_style.dart +++ b/packages/flet/lib/src/utils/overlay_style.dart @@ -1,12 +1,15 @@ -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import '../models/control.dart'; import '../utils/numbers.dart'; +import '../utils/theme.dart'; import 'colors.dart'; -SystemUiOverlayStyle overlayStyleFromJson( - ThemeData? theme, Map json, Brightness? brightness) { +SystemUiOverlayStyle? parseSystemUiOverlayStyle( + dynamic value, ThemeData? theme, Brightness? brightness, + [SystemUiOverlayStyle? defaultValue]) { + if (value == null) return defaultValue; Brightness? invertedBrightness = brightness != null ? brightness == Brightness.light ? Brightness.dark @@ -14,28 +17,28 @@ SystemUiOverlayStyle overlayStyleFromJson( : null; return SystemUiOverlayStyle( - statusBarColor: parseColor(theme, json["status_bar_color"]), + statusBarColor: parseColor(value["status_bar_color"], theme), systemNavigationBarColor: - parseColor(theme, json["system_navigation_bar_color"]), + parseColor(value["system_navigation_bar_color"], theme), systemNavigationBarDividerColor: - parseColor(theme, json["system_navigation_bar_divider_color"]), + parseColor(value["system_navigation_bar_divider_color"], theme), systemStatusBarContrastEnforced: - parseBool(json["enforce_system_status_bar_contrast"]), + parseBool(value["enforce_system_status_bar_contrast"]), systemNavigationBarContrastEnforced: - parseBool(json["enforce_system_navigation_bar_contrast"]), + parseBool(value["enforce_system_navigation_bar_contrast"]), systemNavigationBarIconBrightness: parseBrightness( - json["system_navigation_bar_icon_brightness"], invertedBrightness), + value["system_navigation_bar_icon_brightness"], invertedBrightness), statusBarBrightness: - parseBrightness(json["status_bar_brightness"], brightness), + parseBrightness(value["status_bar_brightness"], brightness), statusBarIconBrightness: parseBrightness( - json["status_bar_icon_brightness"], invertedBrightness)); + value["status_bar_icon_brightness"], invertedBrightness)); } -Brightness? parseBrightness(String? value, [Brightness? defValue]) { - if (value == null) { - return defValue; +extension SystemUiParsers on Control { + SystemUiOverlayStyle? getSystemUiOverlayStyle( + String propertyName, ThemeData? theme, Brightness? brightness, + [SystemUiOverlayStyle? defaultValue]) { + return parseSystemUiOverlayStyle( + get(propertyName), theme, brightness, defaultValue); } - return Brightness.values - .firstWhereOrNull((e) => e.toString() == value.toLowerCase()) ?? - defValue; } diff --git a/packages/flet/lib/src/utils/platform.dart b/packages/flet/lib/src/utils/platform.dart index 0a5989461..c87fa807b 100644 --- a/packages/flet/lib/src/utils/platform.dart +++ b/packages/flet/lib/src/utils/platform.dart @@ -1,5 +1,8 @@ +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; +import '../models/control.dart'; + /// Checks if the current platform is a desktop platform. bool isDesktopPlatform() { return !kIsWeb && @@ -8,23 +11,6 @@ bool isDesktopPlatform() { defaultTargetPlatform == TargetPlatform.linux); } -/// Checks if the current platform is a mobile (iOS or Android) platform. -bool isMobilePlatform() { - return !kIsWeb && - (defaultTargetPlatform == TargetPlatform.iOS || - defaultTargetPlatform == TargetPlatform.android); -} - -/// Checks if the current platform is iOS -bool isiOSPlatform() { - return !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS; -} - -/// Checks if the current platform is Android -bool isAndroidPlatform() { - return !kIsWeb && defaultTargetPlatform == TargetPlatform.android; -} - /// Checks if the current platform is Windows desktop. bool isWindowsDesktop() { return !kIsWeb && (defaultTargetPlatform == TargetPlatform.windows); @@ -40,7 +26,46 @@ bool isLinuxDesktop() { return !kIsWeb && (defaultTargetPlatform == TargetPlatform.linux); } +/// Checks if the current platform is a mobile (iOS or Android) platform. +bool isMobilePlatform() { + return !kIsWeb && + (defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.android); +} + +/// Checks if the current platform is iOS mobile. +bool isIOSMobile() { + return !kIsWeb && (defaultTargetPlatform == TargetPlatform.iOS); +} + +/// Checks if the current platform is Android mobile. +bool isAndroidMobile() { + return !kIsWeb && (defaultTargetPlatform == TargetPlatform.android); +} + +/// Checks if the current platform is an Apple platform (iOS or macOS). +bool isApplePlatform() { + return !kIsWeb && + (defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.macOS); +} + /// Checks if the current platform is a web platform. bool isWebPlatform() { return kIsWeb == true; } + +TargetPlatform? parseTargetPlatform(String? value, + [TargetPlatform? defaultValue]) { + if (value == null) return defaultValue; + return TargetPlatform.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +extension PlatformParsers on Control { + TargetPlatform? getTargetPlatform(String propertyName, + [TargetPlatform? defaultValue]) { + return parseTargetPlatform(get(propertyName), defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/platform_utils_non_web.dart b/packages/flet/lib/src/utils/platform_utils_non_web.dart index 873cde5ea..833596c16 100644 --- a/packages/flet/lib/src/utils/platform_utils_non_web.dart +++ b/packages/flet/lib/src/utils/platform_utils_non_web.dart @@ -5,7 +5,7 @@ bool isProgressiveWebApp() { } String getWebsocketEndpointPath(String uriPath) { - var pagePath = trim(uriPath, "/"); + var pagePath = uriPath.trimSymbol("/"); if (pagePath != "") { pagePath = "$pagePath/"; } @@ -20,5 +20,13 @@ bool isFletWebPyodideMode() { return false; } +bool isMultiView() { + return false; +} + +Map getViewInitialData(int viewId) { + return {}; +} + void openPopupBrowserWindow( String url, String windowName, int minWidth, int minHeight) {} diff --git a/packages/flet/lib/src/utils/platform_utils_web.dart b/packages/flet/lib/src/utils/platform_utils_web.dart index 7ee905d3e..07e73c889 100644 --- a/packages/flet/lib/src/utils/platform_utils_web.dart +++ b/packages/flet/lib/src/utils/platform_utils_web.dart @@ -1,43 +1,74 @@ // ignore: avoid_web_libraries_in_flutter -import 'dart:html' as html; +import 'dart:js_interop'; +import 'dart:ui_web'; + +import 'package:web/web.dart' as web; import 'strings.dart'; bool isProgressiveWebApp() { - return html.window.matchMedia('(display-mode: standalone)').matches || - html.window.matchMedia('(display-mode: fullscreen)').matches || - html.window.matchMedia('(display-mode: minimal-ui)').matches; + return web.window.matchMedia('(display-mode: standalone)').matches || + web.window.matchMedia('(display-mode: fullscreen)').matches || + web.window.matchMedia('(display-mode: minimal-ui)').matches; } String getWebsocketEndpointPath(String uriPath) { - var meta = html.document.head - ?.querySelector("meta[name='flet-websocket-endpoint-path']"); - return trim(meta?.attributes["content"] ?? "ws", "/"); + return fletJS?.webSocketEndpoint.trimSymbol("/") ?? "ws"; } String getFletRouteUrlStrategy() { - var meta = - html.document.head?.querySelector("meta[name='flet-route-url-strategy']"); - return meta != null ? meta.attributes["content"]! : ""; + return fletJS?.routeUrlStrategy ?? ""; } bool isFletWebPyodideMode() { - var meta = html.document.head?.querySelector("meta[name='flet-web-pyodide']"); - return meta != null - ? meta.attributes["content"]?.toLowerCase() == "true" - : false; + return fletJS?.pyodide == true; +} + +bool isMultiView() { + return fletJS?.multiView == true; +} + +String? getHeadMetaContent(String metaName) { + var meta = web.document.head?.querySelector("meta[name='$metaName']"); + return meta?.attributes.getNamedItem("content")?.value; +} + +Map getViewInitialData(int viewId) { + return (views.getInitialData(viewId)?.dartify() ?? {}) as Map; +} + +@JS() +@anonymous +@staticInterop +class FletJS { + external factory FletJS(); } +extension FletJSExtension on FletJS { + external bool get pyodide; + external bool get multiView; + external bool get noCdn; + external String get webSocketEndpoint; + external String get routeUrlStrategy; + external String get canvasKitBaseUrl; + external String get pyodideUrl; + external String get webRenderer; + external bool get appPackageUrl; +} + +@JS('flet') +external FletJS? get fletJS; + void openPopupBrowserWindow( String url, String windowName, int width, int height) { - int screenWidth = html.window.screen!.width!; - int screenHeight = html.window.screen!.height!; - final dualScreenLeft = html.window.screenLeft! < 0 ? -screenWidth : 0; - var toolbarHeight = html.window.outerHeight - html.window.innerHeight!; + int screenWidth = web.window.screen.width; + int screenHeight = web.window.screen.height; + final dualScreenLeft = web.window.screenLeft < 0 ? -screenWidth : 0; + var toolbarHeight = web.window.outerHeight - web.window.innerHeight; //var width = max(minWidth, screenWidth - 300); //var height = max(minHeight, screenHeight - 300); var left = (screenWidth / 2) - (width / 2) + dualScreenLeft; var top = (screenHeight / 2) - (height / 2) - toolbarHeight; - html.window.open(url, windowName, + web.window.open(url, windowName, "top=$top,left=$left,width=$width,height=$height,scrollbars=yes"); } diff --git a/packages/flet/lib/src/utils/responsive.dart b/packages/flet/lib/src/utils/responsive.dart index bd442d13e..18ff2a14d 100644 --- a/packages/flet/lib/src/utils/responsive.dart +++ b/packages/flet/lib/src/utils/responsive.dart @@ -1,52 +1,56 @@ -import 'dart:convert'; - import '../models/control.dart'; import '../utils/numbers.dart'; -Map parseResponsiveNumber( - Control control, String propName, double defaultValue) { - var v = control.attrString(propName, null); +Map? parseResponsiveNumber(dynamic value, double defaultValue) { Map result = {}; - if (v != null) { - var j = json.decode(v); - if (j is! Map) { - j = {"": j}; + + if (value != null) { + if (value is Map) { + result = value.map( + (key, val) => MapEntry(key.toString(), parseDouble(val, 0)!), + ); + } else { + result[""] = parseDouble(value, 0)!; } - result = responsiveNumberFromJson(j); } + if (result[""] == null) { result[""] = defaultValue; } + return result; } -Map responsiveNumberFromJson(Map json) { - return json.map((key, value) => MapEntry(key, parseDouble(value, 0)!)); -} +double getBreakpointNumber( + Map value, double width, Map breakpoints) { + // Defaults + double? selectedValue = value[""]; + double highestMatchedBreakpoint = 0; -double getBreakpointNumber(Map responsiveNumber, double width, - Map breakpoints) { - // default value - double? result = responsiveNumber[""]; + for (final entry in value.entries) { + final bpName = entry.key; + final v = entry.value; - double maxBpWidth = 0; - responsiveNumber.forEach((bpName, respValue) { - if (bpName == "") { - return; - } - var bpWidth = breakpoints[bpName]; - if (bpWidth == null) { - throw Exception("Unknown breakpoint: $bpName"); - } - if (width >= bpWidth && bpWidth >= maxBpWidth) { - maxBpWidth = bpWidth; - result = respValue; + if (bpName.isEmpty) continue; + + final bpWidth = breakpoints[bpName]; + if (bpWidth == null) continue; + + if (width >= bpWidth && bpWidth >= highestMatchedBreakpoint) { + highestMatchedBreakpoint = bpWidth; + selectedValue = v; } - }); + } + + if (selectedValue == null) { + throw Exception("Responsive number not found for width=$width: $value"); + } + return selectedValue; +} - if (result == null) { - throw Exception( - "Responsive number not found for width=$width: $responsiveNumber"); +extension ResponsiveParsers on Control { + Map? getResponsiveNumber( + String propertyName, double defaultValue) { + return parseResponsiveNumber(get(propertyName), defaultValue); } - return result!; } diff --git a/packages/flet/lib/src/utils/session_store_non_web.dart b/packages/flet/lib/src/utils/session_store_non_web.dart index ffbe51e34..473c6fe32 100644 --- a/packages/flet/lib/src/utils/session_store_non_web.dart +++ b/packages/flet/lib/src/utils/session_store_non_web.dart @@ -1,12 +1,14 @@ import 'package:flutter/foundation.dart'; class SessionStore { - static String? get sessionId { - return null; + static String? sessionId; + + static String? getSessionId(String pageUrl) { + return get("sessionId"); } - static set sessionId(String? value) { - // nothing to do + static setSessionId(String pageUrl, String? value) { + set("sessionId", value ?? ""); } static String? get(String name) { diff --git a/packages/flet/lib/src/utils/session_store_web.dart b/packages/flet/lib/src/utils/session_store_web.dart index d1624041f..30b197623 100644 --- a/packages/flet/lib/src/utils/session_store_web.dart +++ b/packages/flet/lib/src/utils/session_store_web.dart @@ -1,26 +1,24 @@ -// ignore: avoid_web_libraries_in_flutter -import 'dart:html' as html; - import 'package:flutter/foundation.dart'; +import 'package:web/web.dart' as web; const String _sessionIdKey = "_flet_session_id"; class SessionStore { - static String? get sessionId { - return get(_sessionIdKey); + static String? getSessionId(String pageUrl) { + return get("$pageUrl$_sessionIdKey"); } - static set sessionId(String? value) { - set(_sessionIdKey, value ?? ""); + static setSessionId(String pageUrl, String? value) { + set("$pageUrl$_sessionIdKey", value ?? ""); } static String? get(String name) { debugPrint("Get session storage $name"); - return html.window.sessionStorage[name]; + return web.window.sessionStorage.getItem(name); } static void set(String name, String value) { debugPrint("Set session storage $name"); - html.window.sessionStorage[name] = value; + web.window.sessionStorage.setItem(name, value); } } diff --git a/packages/flet/lib/src/utils/strings.dart b/packages/flet/lib/src/utils/strings.dart index 14a46a5b5..814a28b2e 100644 --- a/packages/flet/lib/src/utils/strings.dart +++ b/packages/flet/lib/src/utils/strings.dart @@ -1,33 +1,16 @@ -import 'dart:convert'; - -import '../models/control.dart'; - -List? parseStringList(Control control, String propName) { - var v = control.attrString(propName, null); - if (v == null) { - return null; +extension StringExtension on String { + /// Trims the [symbol] from the start of the string. + String trimStart(String symbol) { + return startsWith(symbol) ? substring(symbol.length) : this; } - final jv = json.decode(v); - return (jv as List).map((c) => c as String).toList(); -} - -String trimStart(String str, String symbol) { - if (str.startsWith(symbol)) { - return str.substring(symbol.length); - } else { - return str; + /// Trims the [symbol] from the end of the string. + String trimEnd(String symbol) { + return endsWith(symbol) ? substring(0, length - symbol.length) : this; } -} -String trimEnd(String str, String symbol) { - if (str.endsWith(symbol)) { - return str.substring(0, str.length - symbol.length); - } else { - return str; + /// Trims the [symbol] from both the start and end of the string. + String trimSymbol(String symbol) { + return trimStart(symbol).trimEnd(symbol); } } - -String trim(String str, String symbol) { - return trimEnd(trimStart(str, symbol), symbol); -} diff --git a/packages/flet/lib/src/utils/text.dart b/packages/flet/lib/src/utils/text.dart index 1bb494ab1..d3f9f1868 100644 --- a/packages/flet/lib/src/utils/text.dart +++ b/packages/flet/lib/src/utils/text.dart @@ -1,11 +1,8 @@ -import 'dart:convert'; - import 'package:collection/collection.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import '../models/control.dart'; -import '../models/control_tree_view_model.dart'; import '../utils/box.dart'; import '../utils/drawing.dart'; import '../utils/numbers.dart'; @@ -79,124 +76,92 @@ FontWeight? getFontWeight(String? weightName, [FontWeight? defaultWeight]) { } } -List parseTextSpans( - ThemeData theme, - ControlTreeViewModel viewModel, - bool parentDisabled, - void Function(String, String, String)? sendControlEvent) { - return viewModel.children - .map((c) => parseInlineSpan(theme, c, parentDisabled, sendControlEvent)) - .whereNotNull() +List parseTextSpans(List spans, ThemeData theme, + [void Function(Control, String, [dynamic eventData])? sendControlEvent]) { + return spans + .map((span) => parseInlineSpan(span, theme, sendControlEvent)) + .nonNulls .toList(); } -InlineSpan? parseInlineSpan( - ThemeData theme, - ControlTreeViewModel spanViewModel, - bool parentDisabled, - void Function(String, String, String)? sendControlEvent) { - if (spanViewModel.control.type == "textspan") { - bool disabled = spanViewModel.control.isDisabled || parentDisabled; - var onClick = spanViewModel.control.attrBool("onClick", false)!; - String url = spanViewModel.control.attrString("url", "")!; - String? urlTarget = spanViewModel.control.attrString("urlTarget"); - return TextSpan( - text: spanViewModel.control.attrString("text"), - style: parseTextStyle(theme, spanViewModel.control, "style"), - spellOut: spanViewModel.control.attrBool("spellOut"), - semanticsLabel: spanViewModel.control.attrString("semanticsLabel"), - children: parseTextSpans( - theme, spanViewModel, parentDisabled, sendControlEvent), - mouseCursor: onClick && !disabled && sendControlEvent != null - ? SystemMouseCursors.click - : null, - recognizer: - (onClick || url != "") && !disabled && sendControlEvent != null - ? (TapGestureRecognizer() - ..onTap = () { - debugPrint("TextSpan ${spanViewModel.control.id} clicked!"); - if (url != "") { - openWebBrowser(url, webWindowName: urlTarget); - } - if (onClick) { - sendControlEvent(spanViewModel.control.id, "click", ""); - } - }) - : null, - onEnter: spanViewModel.control.attrBool("onEnter", false)! && - !disabled && - sendControlEvent != null - ? (event) { - debugPrint("TextSpan ${spanViewModel.control.id} entered!"); - sendControlEvent(spanViewModel.control.id, "enter", ""); - } - : null, - onExit: spanViewModel.control.attrBool("onExit", false)! && - !disabled && - sendControlEvent != null - ? (event) { - debugPrint("TextSpan ${spanViewModel.control.id} exited!"); - sendControlEvent(spanViewModel.control.id, "exit", ""); - } - : null, - ); - } - return null; +TextSpan? parseInlineSpan(Control span, ThemeData theme, + [void Function(Control, String, [dynamic eventData])? sendControlEvent]) { + span.notifyParent = true; + var onClick = span.getBool("on_click", false)!; + var url = span.getString("url"); + var urlTarget = span.getString("url_target"); + + return TextSpan( + text: span.getString("text"), + style: parseTextStyle(span.get("style"), theme), + spellOut: span.getBool("spell_out"), + semanticsLabel: span.getString("semantics_label"), + children: parseTextSpans(span.children("spans"), theme, sendControlEvent), + mouseCursor: onClick && !span.disabled && sendControlEvent != null + ? SystemMouseCursors.click + : null, + recognizer: + (onClick || url != null) && !span.disabled && sendControlEvent != null + ? (TapGestureRecognizer() + ..onTap = () { + if (url != null) openWebBrowser(url, webWindowName: urlTarget); + if (onClick) sendControlEvent(span, "click"); + }) + : null, + onEnter: span.getBool("on_enter", false)! && + !span.disabled && + sendControlEvent != null + ? (event) => sendControlEvent(span, "enter") + : null, + onExit: span.getBool("on_exit", false)! && + !span.disabled && + sendControlEvent != null + ? (event) => sendControlEvent(span, "exit") + : null, + ); } TextAlign? parseTextAlign(String? value, [TextAlign? defaultValue]) { - if (value == null) { - return defaultValue; - } + if (value == null) return defaultValue; return TextAlign.values.firstWhereOrNull( (a) => a.name.toLowerCase() == value.toLowerCase()) ?? defaultValue; } TextOverflow? parseTextOverflow(String? value, [TextOverflow? defaultValue]) { - if (value == null) { - return defaultValue; - } + if (value == null) return defaultValue; return TextOverflow.values.firstWhereOrNull( (a) => a.name.toLowerCase() == value.toLowerCase()) ?? defaultValue; } +TextDecorationStyle? parseTextDecorationStyle(String? value, + [TextDecorationStyle? defaultValue]) { + if (value == null) return defaultValue; + return TextDecorationStyle.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + TextCapitalization? parseTextCapitalization(String? value, [TextCapitalization? defaultValue]) { - if (value == null) { - return defaultValue; - } + if (value == null) return defaultValue; return TextCapitalization.values.firstWhereOrNull( (a) => a.name.toLowerCase() == value.toLowerCase()) ?? defaultValue; } TextBaseline? parseTextBaseline(String? value, [TextBaseline? defaultValue]) { - if (value == null) { - return defaultValue; - } + if (value == null) return defaultValue; return TextBaseline.values.firstWhereOrNull( (a) => a.name.toLowerCase() == value.toLowerCase()) ?? defaultValue; } -TextStyle? parseTextStyle(ThemeData theme, Control control, String propName) { - dynamic j; - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - j = json.decode(v); - return textStyleFromJson(theme, j); -} - -TextStyle? textStyleFromJson(ThemeData theme, Map? json) { - if (json == null) { - return null; - } - - var fontWeight = json["weight"]; +TextStyle? parseTextStyle(dynamic value, ThemeData theme, + [TextStyle? defaultValue]) { + if (value == null) return defaultValue; + var fontWeight = value["weight"]; List? variations; if (fontWeight != null && fontWeight.startsWith("w")) { @@ -206,7 +171,7 @@ TextStyle? textStyleFromJson(ThemeData theme, Map? json) { } List decorations = []; - var decor = parseInt(json["decoration"], 0)!; + var decor = parseInt(value["decoration"], 0)!; if (decor & 0x1 > 0) { decorations.add(TextDecoration.underline); } @@ -218,41 +183,86 @@ TextStyle? textStyleFromJson(ThemeData theme, Map? json) { } return TextStyle( - fontSize: parseDouble(json["size"]), + fontSize: parseDouble(value["size"]), fontWeight: getFontWeight(fontWeight), - fontStyle: parseBool(json["italic"], false)! ? FontStyle.italic : null, - fontFamily: json["font_family"], + fontStyle: parseBool(value["italic"], false)! ? FontStyle.italic : null, + fontFamily: value["font_family"], fontVariations: variations, - height: parseDouble(json["height"]), + height: parseDouble(value["height"]), decoration: decorations.isNotEmpty ? TextDecoration.combine(decorations) : null, - decorationStyle: json["decoration_style"] != null - ? TextDecorationStyle.values.firstWhereOrNull((v) => - v.name.toLowerCase() == json["decoration_style"].toLowerCase()) - : null, - decorationColor: parseColor(theme, json["decoration_color"]), - decorationThickness: parseDouble(json["decoration_thickness"]), - color: parseColor(theme, json["color"]), - backgroundColor: parseColor(theme, json["bgcolor"]), - shadows: json["shadow"] != null - ? boxShadowsFromJSON(theme, json["shadow"]) - : null, - foreground: json["foreground"] != null - ? paintFromJSON(theme, json["foreground"]) - : null, - letterSpacing: parseDouble(json['letter_spacing']), - overflow: parseTextOverflow(json['overflow']), - wordSpacing: parseDouble(json['word_spacing']), - textBaseline: parseTextBaseline(json['text_baseline']), + decorationStyle: parseTextDecorationStyle(value["decoration_style"]), + decorationColor: parseColor(value["decoration_color"], theme), + decorationThickness: parseDouble(value["decoration_thickness"]), + color: parseColor(value["color"], theme), + backgroundColor: parseColor(value["bgcolor"], theme), + shadows: parseBoxShadows(value["shadow"], theme), + foreground: parsePaint(value["foreground"], theme), + letterSpacing: parseDouble(value['letter_spacing']), + overflow: parseTextOverflow(value['overflow']), + wordSpacing: parseDouble(value['word_spacing']), + textBaseline: parseTextBaseline(value['text_baseline']), ); } WidgetStateProperty? parseWidgetStateTextStyle( - ThemeData theme, Control control, String propName) { - var v = control.attrString(propName); - if (v == null) { - return null; - } + dynamic value, ThemeData theme, + {TextStyle? defaultTextStyle, + WidgetStateProperty? defaultValue}) { + if (value == null) return defaultValue; return getWidgetStateProperty( - jsonDecode(v), (jv) => textStyleFromJson(theme, jv), null); + value, (jv) => parseTextStyle(theme, jv), defaultTextStyle); +} + +extension TextParsers on Control { + TextStyle? getTextStyle(String propertyName, ThemeData theme, + [TextStyle? defaultValue]) { + return parseTextStyle(get(propertyName), theme, defaultValue); + } + + TextAlign? getTextAlign(String propertyName, [TextAlign? defaultValue]) { + return parseTextAlign(get(propertyName), defaultValue); + } + + TextOverflow? getTextOverflow(String propertyName, + [TextOverflow? defaultValue]) { + return parseTextOverflow(get(propertyName), defaultValue); + } + + TextDecorationStyle? getTextDecorationStyle(String propertyName, + [TextDecorationStyle? defaultValue]) { + return parseTextDecorationStyle(get(propertyName), defaultValue); + } + + TextCapitalization? getTextCapitalization(String propertyName, + [TextCapitalization? defaultValue]) { + return parseTextCapitalization(get(propertyName), defaultValue); + } + + TextBaseline? getTextBaseline(String propertyName, + [TextBaseline? defaultValue]) { + return parseTextBaseline(get(propertyName), defaultValue); + } + + WidgetStateProperty? getWidgetStateTextStyle( + String propertyName, ThemeData theme, + {TextStyle? defaultTextStyle, + WidgetStateProperty? defaultValue}) { + return parseWidgetStateTextStyle(get(propertyName), theme, + defaultTextStyle: defaultTextStyle, defaultValue: defaultValue); + } +} + +extension TextSelectionExtension on TextSelection { + Map toMap() => { + "start": start, + "end": end, + "base_offset": baseOffset, + "extent_offset": extentOffset, + "affinity": affinity.name, + "directional": isDirectional, + "collapsed": isCollapsed, + "valid": isValid, + "normalized": isNormalized, + }; } diff --git a/packages/flet/lib/src/utils/textfield.dart b/packages/flet/lib/src/utils/textfield.dart index 133faf540..d0e764c69 100644 --- a/packages/flet/lib/src/utils/textfield.dart +++ b/packages/flet/lib/src/utils/textfield.dart @@ -1,27 +1,14 @@ -import 'dart:convert'; - import 'package:flutter/services.dart'; import '../models/control.dart'; import '../utils/numbers.dart'; -FilteringTextInputFormatter? parseInputFilter( - Control control, String propName) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - - final j1 = json.decode(v); - return inputFilterFromJSON(j1); -} - -FilteringTextInputFormatter? inputFilterFromJSON(dynamic json) { - var regexString = json["regex_string"]?.toString(); - if (json == null || regexString == null) { - return null; - } - return CustomFilteringTextInputFormatter.fromJSON(json); +FilteringTextInputFormatter? parseInputFilter(dynamic value, + [FilteringTextInputFormatter? defaultValue]) { + if (value == null) return defaultValue; + var regexString = value["regex_string"]?.toString(); + if (regexString == null) return defaultValue; + return CustomFilteringTextInputFormatter.fromMap(value); } class CustomFilteringTextInputFormatter extends FilteringTextInputFormatter { @@ -31,20 +18,20 @@ class CustomFilteringTextInputFormatter extends FilteringTextInputFormatter { {bool allow = true, String replacementString = ""}) : super(_pattern, allow: allow, replacementString: replacementString); - // Factory constructor to create an instance from JSON - factory CustomFilteringTextInputFormatter.fromJSON( - Map json) { + // Factory constructor to create an instance from a map + factory CustomFilteringTextInputFormatter.fromMap( + Map value) { final pattern = RegExp( - json["regex_string"]?.toString() ?? "", - multiLine: parseBool(json["multiline"], false)!, - unicode: parseBool(json["unicode"], false)!, - caseSensitive: parseBool(json["case_sensitive"], true)!, - dotAll: parseBool(json["dot_all"], false)!, + value["regex_string"]?.toString() ?? "", + multiLine: parseBool(value["multiline"], false)!, + unicode: parseBool(value["unicode"], false)!, + caseSensitive: parseBool(value["case_sensitive"], true)!, + dotAll: parseBool(value["dot_all"], false)!, ); return CustomFilteringTextInputFormatter._(pattern, - allow: parseBool(json["allow"], true)!, - replacementString: json["replacement_string"]?.toString() ?? ""); + allow: parseBool(value["allow"], true)!, + replacementString: value["replacement_string"]?.toString() ?? ""); } @override @@ -57,3 +44,90 @@ class CustomFilteringTextInputFormatter extends FilteringTextInputFormatter { return oldValue; } } + +class TextCapitalizationFormatter extends TextInputFormatter { + final TextCapitalization capitalization; + + TextCapitalizationFormatter(this.capitalization); + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, TextEditingValue newValue) { + String text = ''; + + switch (capitalization) { + case TextCapitalization.words: + text = capitalizeFirstofEach(newValue.text); + break; + case TextCapitalization.sentences: + List sentences = newValue.text.split('.'); + for (int i = 0; i < sentences.length; i++) { + sentences[i] = inCaps(sentences[i]); + } + text = sentences.join('.'); + break; + case TextCapitalization.characters: + text = allInCaps(newValue.text); + break; + case TextCapitalization.none: + text = newValue.text; + break; + } + + return TextEditingValue( + text: text, + selection: newValue.selection, + ); + } + + /// 'Hello world' + static String inCaps(String text) { + if (text.isEmpty) { + return text; + } + String result = ''; + for (int i = 0; i < text.length; i++) { + if (text[i] != ' ') { + result += '${text[i].toUpperCase()}${text.substring(i + 1)}'; + break; + } else { + result += text[i]; + } + } + return result; + } + + /// 'HELLO WORLD' + static String allInCaps(String text) => text.toUpperCase(); + + /// 'Hello World' + static String capitalizeFirstofEach(String text) => text + .replaceAll(RegExp(' +'), ' ') + .split(" ") + .map((str) => inCaps(str)) + .join(" "); +} + +class CustomNumberFormatter extends TextInputFormatter { + final String pattern; + + CustomNumberFormatter(this.pattern); + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, TextEditingValue newValue) { + final regExp = RegExp(pattern); + if (regExp.hasMatch(newValue.text)) { + return newValue; + } + // If newValue is invalid, keep the old value + return oldValue; + } +} + +extension InputFormatterParsers on Control { + FilteringTextInputFormatter? getTextInputFormatter(String propertyName, + [FilteringTextInputFormatter? defaultValue]) { + return parseInputFilter(get(propertyName), defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/theme.dart b/packages/flet/lib/src/utils/theme.dart index 5514035d8..7a9f39a2e 100644 --- a/packages/flet/lib/src/utils/theme.dart +++ b/packages/flet/lib/src/utils/theme.dart @@ -1,24 +1,24 @@ -import 'dart:convert'; - import 'package:collection/collection.dart'; -import 'package:flet/src/utils/locale.dart'; -import 'package:flet/src/utils/others.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../models/control.dart'; +import '../utils/transforms.dart'; import 'alignment.dart'; import 'borders.dart'; import 'box.dart'; import 'buttons.dart'; import 'colors.dart'; import 'dismissible.dart'; -import 'drawing.dart'; import 'edge_insets.dart'; +import 'geometry.dart'; import 'icons.dart'; +import 'locale.dart'; import 'material_state.dart'; import 'menu.dart'; +import 'misc.dart'; import 'mouse.dart'; import 'numbers.dart'; import 'overlay_style.dart'; @@ -32,64 +32,71 @@ class SystemUiOverlayStyleTheme SystemUiOverlayStyleTheme(this.systemUiOverlayStyle); @override - ThemeExtension copyWith() { + SystemUiOverlayStyleTheme copyWith() { return SystemUiOverlayStyleTheme(systemUiOverlayStyle); } @override - ThemeExtension lerp( - covariant ThemeExtension? other, double t) { - return this; + SystemUiOverlayStyleTheme lerp( + covariant SystemUiOverlayStyleTheme? other, double t) { + if (other is! SystemUiOverlayStyleTheme) { + return this; + } + return other; + } + + @override + bool operator ==(Object other) { + return systemUiOverlayStyle == + (other as SystemUiOverlayStyleTheme).systemUiOverlayStyle; } + + @override + int get hashCode => systemUiOverlayStyle.hashCode; } CupertinoThemeData parseCupertinoTheme( - Control control, String propName, Brightness? brightness, + dynamic value, BuildContext context, Brightness? brightness, {ThemeData? parentTheme}) { - var theme = parseTheme(control, propName, brightness); + var theme = parseTheme(value, context, brightness); var cupertinoTheme = MaterialBasedCupertinoThemeData(materialTheme: theme); return fixCupertinoTheme(cupertinoTheme, theme); } CupertinoThemeData fixCupertinoTheme( CupertinoThemeData cupertinoTheme, ThemeData theme) { - return cupertinoTheme.copyWith( + var r = cupertinoTheme.copyWith( applyThemeToAll: true, barBackgroundColor: theme.colorScheme.surface, textTheme: cupertinoTheme.textTheme.copyWith( navTitleTextStyle: cupertinoTheme.textTheme.navTitleTextStyle .copyWith(color: theme.colorScheme.onSurface))); + return r; } -ThemeMode? parseThemeMode(String? value, [ThemeMode? defValue]) { - if (value == null) { - return defValue; - } - return ThemeMode.values.firstWhereOrNull( +Brightness? parseBrightness(String? value, [Brightness? defaultValue]) { + if (value == null) return defaultValue; + return Brightness.values.firstWhereOrNull( (e) => e.name.toLowerCase() == value.toLowerCase()) ?? - defValue; + defaultValue; } -ThemeData parseTheme(Control control, String propName, Brightness? brightness, - {ThemeData? parentTheme}) { - dynamic j; - var v = control.attrString(propName); - if (v != null) { - j = json.decode(v); - } - return themeFromJson(j, brightness, parentTheme); +ThemeMode? parseThemeMode(String? value, [ThemeMode? defaultValue]) { + if (value == null) return defaultValue; + return ThemeMode.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; } -ThemeData themeFromJson(Map? json, Brightness? brightness, - ThemeData? parentTheme) { +ThemeData parseTheme( + dynamic value, BuildContext context, Brightness? brightness, + {ThemeData? parentTheme}) { ThemeData? theme = parentTheme; - var primarySwatch = parseColor(theme, json?["primary_swatch"]); - var colorSchemeSeed = parseColor(theme, json?["color_scheme_seed"]); + var primarySwatch = parseColor(value?["primary_swatch"], theme); + var colorSchemeSeed = parseColor(value?["color_scheme_seed"], theme); - if (colorSchemeSeed != null) { - primarySwatch = null; - } + if (colorSchemeSeed != null) primarySwatch = null; if (colorSchemeSeed == null && primarySwatch == null) { colorSchemeSeed = Colors.blue; @@ -97,515 +104,467 @@ ThemeData themeFromJson(Map? json, Brightness? brightness, // create new theme theme ??= ThemeData( - primarySwatch: - primarySwatch != null ? primarySwatch as MaterialColor : null, - colorSchemeSeed: colorSchemeSeed, - fontFamily: json?["font_family"], - brightness: brightness, - useMaterial3: json?["use_material3"] ?? primarySwatch == null); + primarySwatch: + primarySwatch != null ? primarySwatch as MaterialColor : null, + colorSchemeSeed: colorSchemeSeed, + fontFamily: value?["font_family"], + brightness: brightness, + useMaterial3: value?["use_material3"] ?? primarySwatch == null, + ); theme = theme.copyWith( + extensions: { + SystemUiOverlayStyleTheme(value?["system_overlay_style"] != null + ? parseSystemUiOverlayStyle( + value?["system_overlay_style"], theme, brightness) + : null) + }, visualDensity: - parseVisualDensity(json?["visual_density"], theme.visualDensity)!, + parseVisualDensity(value?["visual_density"], theme.visualDensity)!, pageTransitionsTheme: parsePageTransitions( - json?["page_transitions"], theme.pageTransitionsTheme)!, - colorScheme: parseColorScheme(theme, json?["color_scheme"]), - textTheme: parseTextTheme(theme, theme.textTheme, json?["text_theme"]), + value?["page_transitions"], theme.pageTransitionsTheme)!, + colorScheme: parseColorScheme(value?["color_scheme"], theme), + textTheme: parseTextTheme(value?["text_theme"], theme, theme.textTheme), primaryTextTheme: parseTextTheme( - theme, theme.primaryTextTheme, json?["primary_text_theme"]), - scrollbarTheme: parseScrollBarTheme(theme, json?["scrollbar_theme"]), - tabBarTheme: parseTabBarTheme(theme, json?["tabs_theme"]), - splashColor: parseColor(theme, json?["splash_color"]), - highlightColor: parseColor(theme, json?["highlight_color"]), - hoverColor: parseColor(theme, json?["hover_color"]), - focusColor: parseColor(theme, json?["focus_color"]), - unselectedWidgetColor: parseColor(theme, json?["unselected_control_color"]), - disabledColor: parseColor(theme, json?["disabled_color"]), - canvasColor: parseColor(theme, json?["canvas_color"]), - scaffoldBackgroundColor: parseColor(theme, json?["scaffold_bgcolor"]), - cardColor: parseColor(theme, json?["card_color"]), - dividerColor: parseColor(theme, json?["divider_color"]), - // TODO: deprecated in v0.27.0, and to be removed in v0.30.0 - dialogBackgroundColor: parseColor(theme, json?["dialog_bgcolor"]), - indicatorColor: parseColor(theme, json?["indicator_color"]), - hintColor: parseColor(theme, json?["hint_color"]), - shadowColor: parseColor(theme, json?["shadow_color"]), - secondaryHeaderColor: parseColor(theme, json?["secondary_header_color"]), - primaryColor: parseColor(theme, json?["primary_color"]), - primaryColorLight: parseColor(theme, json?["primary_color_light"]), - primaryColorDark: parseColor(theme, json?["primary_color_dark"]), - dialogTheme: parseDialogTheme(theme, json?["dialog_theme"]), - bottomSheetTheme: parseBottomSheetTheme(theme, json?["bottom_sheet_theme"]), - cardTheme: parseCardTheme(theme, json?["card_theme"]), - chipTheme: parseChipTheme(theme, json?["chip_theme"]), + value?["primary_text_theme"], theme, theme.primaryTextTheme), + scrollbarTheme: parseScrollBarTheme(value?["scrollbar_theme"], theme), + tabBarTheme: parseTabBarTheme(value?["tabs_theme"], theme), + splashColor: parseColor(value?["splash_color"], theme), + highlightColor: parseColor(value?["highlight_color"], theme), + hoverColor: parseColor(value?["hover_color"], theme), + focusColor: parseColor(value?["focus_color"], theme), + unselectedWidgetColor: + parseColor(value?["unselected_control_color"], theme), + disabledColor: parseColor(value?["disabled_color"], theme), + canvasColor: parseColor(value?["canvas_color"], theme), + scaffoldBackgroundColor: parseColor(value?["scaffold_bgcolor"], theme), + cardColor: parseColor(value?["card_color"], theme), + dividerColor: parseColor(value?["divider_color"], theme), + indicatorColor: parseColor(value?["indicator_color"], theme), + hintColor: parseColor(value?["hint_color"], theme), + shadowColor: parseColor(value?["shadow_color"], theme), + secondaryHeaderColor: parseColor(value?["secondary_header_color"], theme), + primaryColor: parseColor(value?["primary_color"], theme), + primaryColorLight: parseColor(value?["primary_color_light"], theme), + primaryColorDark: parseColor(value?["primary_color_dark"], theme), + dialogTheme: parseDialogTheme(value?["dialog_theme"], theme), + bottomSheetTheme: + parseBottomSheetTheme(value?["bottom_sheet_theme"], theme), + cardTheme: parseCardTheme(value?["card_theme"], theme), + chipTheme: parseChipTheme(value?["chip_theme"], theme), floatingActionButtonTheme: parseFloatingActionButtonTheme( - theme, json?["floating_action_button_theme"]), + value?["floating_action_button_theme"], theme), bottomAppBarTheme: - parseBottomAppBarTheme(theme, json?["bottom_app_bar_theme"]), - checkboxTheme: parseCheckboxTheme(theme, json?["checkbox_theme"]), - radioTheme: parseRadioTheme(theme, json?["radio_theme"]), - badgeTheme: parseBadgeTheme(theme, json?["badge_theme"]), - switchTheme: parseSwitchTheme(theme, json?["switch_theme"]), - dividerTheme: parseDividerTheme(theme, json?["divider_theme"]), - snackBarTheme: parseSnackBarTheme(theme, json?["snackbar_theme"]), - bannerTheme: parseBannerTheme(theme, json?["banner_theme"]), - datePickerTheme: parseDatePickerTheme(theme, json?["date_picker_theme"]), + parseBottomAppBarTheme(value?["bottom_app_bar_theme"], theme), + checkboxTheme: parseCheckboxTheme(value?["checkbox_theme"], theme), + radioTheme: parseRadioTheme(value?["radio_theme"], theme), + badgeTheme: parseBadgeTheme(value?["badge_theme"], theme), + switchTheme: parseSwitchTheme(value?["switch_theme"], theme), + dividerTheme: parseDividerTheme(value?["divider_theme"], theme), + snackBarTheme: parseSnackBarTheme(value?["snackbar_theme"], theme), + bannerTheme: parseBannerTheme(value?["banner_theme"], theme), + datePickerTheme: parseDatePickerTheme(value?["date_picker_theme"], theme), navigationRailTheme: - parseNavigationRailTheme(theme, json?["navigation_rail_theme"]), - appBarTheme: parseAppBarTheme(theme, json?["appbar_theme"]), + parseNavigationRailTheme(value?["navigation_rail_theme"], theme), + appBarTheme: parseAppBarTheme(value?["appbar_theme"], theme), dropdownMenuTheme: - parseDropdownMenuTheme(theme, json?["dropdown_menu_theme"]), - listTileTheme: parseListTileTheme(theme, json?["list_tile_theme"]), - tooltipTheme: parseTooltipTheme(theme, json?["tooltip_theme"]), + parseDropdownMenuTheme(value?["dropdown_menu_theme"], theme), + listTileTheme: parseListTileTheme(value?["list_tile_theme"], theme), + tooltipTheme: parseTooltipTheme(value?["tooltip_theme"], context), expansionTileTheme: - parseExpansionTileTheme(theme, json?["expansion_tile_theme"]), - sliderTheme: parseSliderTheme(theme, json?["slider_theme"]), + parseExpansionTileTheme(value?["expansion_tile_theme"], theme), + sliderTheme: parseSliderTheme(value?["slider_theme"], theme), progressIndicatorTheme: - parseProgressIndicatorTheme(theme, json?["progress_indicator_theme"]), - popupMenuTheme: parsePopupMenuTheme(theme, json?["popup_menu_theme"]), - searchBarTheme: parseSearchBarTheme(theme, json?["search_bar_theme"]), - searchViewTheme: parseSearchViewTheme(theme, json?["search_view_theme"]), + parseProgressIndicatorTheme(value?["progress_indicator_theme"], theme), + popupMenuTheme: parsePopupMenuTheme(value?["popup_menu_theme"], theme), + searchBarTheme: parseSearchBarTheme(value?["search_bar_theme"], theme), + searchViewTheme: parseSearchViewTheme(value?["search_view_theme"], theme), navigationDrawerTheme: - parseNavigationDrawerTheme(theme, json?["navigation_drawer_theme"]), - navigationBarTheme: parseNavigationBarTheme( - theme, - json?["navigation_bar_theme"], - ), - dataTableTheme: parseDataTableTheme(theme, json?["data_table_theme"]), - buttonTheme: parseButtonTheme(theme, json?["button_theme"]), + parseNavigationDrawerTheme(value?["navigation_drawer_theme"], theme), + navigationBarTheme: + parseNavigationBarTheme(value?["navigation_bar_theme"], theme), + dataTableTheme: parseDataTableTheme(value?["data_table_theme"], context), + buttonTheme: parseButtonTheme(value?["button_theme"], theme), elevatedButtonTheme: - parseElevatedButtonTheme(theme, json?["elevated_button_theme"]), + parseElevatedButtonTheme(value?["elevated_button_theme"], theme), outlinedButtonTheme: - parseOutlinedButtonTheme(theme, json?["outlined_button_theme"]), - textButtonTheme: parseTextButtonTheme(theme, json?["text_button_theme"]), + parseOutlinedButtonTheme(value?["outlined_button_theme"], theme), + textButtonTheme: parseTextButtonTheme(value?["text_button_theme"], theme), filledButtonTheme: - parseFilledButtonTheme(theme, json?["filled_button_theme"]), - iconButtonTheme: parseIconButtonTheme(theme, json?["icon_button_theme"]), - segmentedButtonTheme: parseSegmentedButtonTheme( - theme, - json?["segmented_button_theme"], - ), - iconTheme: parseIconTheme(theme, json?["icon_theme"]), - timePickerTheme: parseTimePickerTheme(theme, json?["time_picker_theme"]), + parseFilledButtonTheme(value?["filled_button_theme"], theme), + iconButtonTheme: parseIconButtonTheme(value?["icon_button_theme"], theme), + segmentedButtonTheme: + parseSegmentedButtonTheme(value?["segmented_button_theme"], theme), + iconTheme: parseIconTheme(value?["icon_theme"], theme), + timePickerTheme: parseTimePickerTheme(value?["time_picker_theme"], theme), ); - var systemOverlayStyle = json?["system_overlay_style"] != null - ? overlayStyleFromJson(theme, json?["system_overlay_style"], brightness) - : null; - return theme.copyWith( - extensions: {SystemUiOverlayStyleTheme(systemOverlayStyle)}, cupertinoOverrideTheme: fixCupertinoTheme( MaterialBasedCupertinoThemeData(materialTheme: theme), theme)); } -ColorScheme? parseColorScheme(ThemeData theme, Map? j) { - if (j == null) { - return null; - } +ColorScheme? parseColorScheme(Map? value, ThemeData theme, + [ColorScheme? defaultValue]) { + if (value == null) return defaultValue; return theme.colorScheme.copyWith( - primary: parseColor(theme, j["primary"]), - onPrimary: parseColor(theme, j["on_primary"]), - primaryContainer: parseColor(theme, j["primary_container"]), - onPrimaryContainer: parseColor(theme, j["on_primary_container"]), - secondary: parseColor(theme, j["secondary"]), - onSecondary: parseColor(theme, j["on_secondary"]), - secondaryContainer: parseColor(theme, j["secondary_container"]), - onSecondaryContainer: parseColor(theme, j["on_secondary_container"]), - tertiary: parseColor(theme, j["tertiary"]), - onTertiary: parseColor(theme, j["on_tertiary"]), - tertiaryContainer: parseColor(theme, j["tertiary_container"]), - onTertiaryContainer: parseColor(theme, j["on_tertiary_container"]), - error: parseColor(theme, j["error"]), - onError: parseColor(theme, j["on_error"]), - errorContainer: parseColor(theme, j["error_container"]), - onErrorContainer: parseColor(theme, j["on_error_container"]), - surface: parseColor(theme, j["surface"]), - onSurface: parseColor(theme, j["on_surface"]), - surfaceContainerHighest: parseColor(theme, j["surface_variant"]), - onSurfaceVariant: parseColor(theme, j["on_surface_variant"]), - outline: parseColor(theme, j["outline"]), - outlineVariant: parseColor(theme, j["outline_variant"]), - shadow: parseColor(theme, j["shadow"]), - scrim: parseColor(theme, j["scrim"]), - inverseSurface: parseColor(theme, j["inverse_surface"]), - onInverseSurface: parseColor(theme, j["on_inverse_surface"]), - inversePrimary: parseColor(theme, j["inverse_primary"]), - surfaceTint: parseColor(theme, j["surface_tint"]), - onPrimaryFixed: parseColor(theme, j["on_primary_fixed"]), - onSecondaryFixed: parseColor(theme, j["on_secondary_fixed"]), - onTertiaryFixed: parseColor(theme, j["on_tertiary_fixed"]), - onPrimaryFixedVariant: parseColor(theme, j["on_primary_fixed_variant"]), - onSecondaryFixedVariant: parseColor(theme, j["on_secondary_fixed_variant"]), - onTertiaryFixedVariant: parseColor(theme, j["on_tertiary_fixed_variant"]), - primaryFixed: parseColor(theme, j["primary_fixed"]), - secondaryFixed: parseColor(theme, j["secondary_fixed"]), - tertiaryFixed: parseColor(theme, j["tertiary_fixed"]), - primaryFixedDim: parseColor(theme, j["primary_fixed_dim"]), - secondaryFixedDim: parseColor(theme, j["secondary_fixed_dim"]), - surfaceBright: parseColor(theme, j["surface_bright"]), - surfaceContainer: parseColor(theme, j["surface_container"]), - surfaceContainerHigh: parseColor(theme, j["surface_container_high"]), - surfaceContainerLow: parseColor(theme, j["surface_container_low"]), - surfaceContainerLowest: parseColor(theme, j["surface_container_lowest"]), - surfaceDim: parseColor(theme, j["surface_dim"]), - tertiaryFixedDim: parseColor(theme, j["tertiary_fixed_dim"]), + primary: parseColor(value["primary"], theme), + onPrimary: parseColor(value["on_primary"], theme), + primaryContainer: parseColor(value["primary_container"], theme), + onPrimaryContainer: parseColor(value["on_primary_container"], theme), + secondary: parseColor(value["secondary"], theme), + onSecondary: parseColor(value["on_secondary"], theme), + secondaryContainer: parseColor(value["secondary_container"], theme), + onSecondaryContainer: parseColor(value["on_secondary_container"], theme), + tertiary: parseColor(value["tertiary"], theme), + onTertiary: parseColor(value["on_tertiary"], theme), + tertiaryContainer: parseColor(value["tertiary_container"], theme), + onTertiaryContainer: parseColor(value["on_tertiary_container"], theme), + error: parseColor(value["error"], theme), + onError: parseColor(value["on_error"], theme), + errorContainer: parseColor(value["error_container"], theme), + onErrorContainer: parseColor(value["on_error_container"], theme), + surface: parseColor(value["surface"], theme), + onSurface: parseColor(value["on_surface"], theme), + surfaceContainerHighest: parseColor(value["surface_variant"], theme), + onSurfaceVariant: parseColor(value["on_surface_variant"], theme), + outline: parseColor(value["outline"], theme), + outlineVariant: parseColor(value["outline_variant"], theme), + shadow: parseColor(value["shadow"], theme), + scrim: parseColor(value["scrim"], theme), + inverseSurface: parseColor(value["inverse_surface"], theme), + onInverseSurface: parseColor(value["on_inverse_surface"], theme), + inversePrimary: parseColor(value["inverse_primary"], theme), + surfaceTint: parseColor(value["surface_tint"], theme), + onPrimaryFixed: parseColor(value["on_primary_fixed"], theme), + onSecondaryFixed: parseColor(value["on_secondary_fixed"], theme), + onTertiaryFixed: parseColor(value["on_tertiary_fixed"], theme), + onPrimaryFixedVariant: parseColor(value["on_primary_fixed_variant"], theme), + onSecondaryFixedVariant: + parseColor(value["on_secondary_fixed_variant"], theme), + onTertiaryFixedVariant: + parseColor(value["on_tertiary_fixed_variant"], theme), + primaryFixed: parseColor(value["primary_fixed"], theme), + secondaryFixed: parseColor(value["secondary_fixed"], theme), + tertiaryFixed: parseColor(value["tertiary_fixed"], theme), + primaryFixedDim: parseColor(value["primary_fixed_dim"], theme), + secondaryFixedDim: parseColor(value["secondary_fixed_dim"], theme), + surfaceBright: parseColor(value["surface_bright"], theme), + surfaceContainer: parseColor(value["surface_container"], theme), + surfaceContainerHigh: parseColor(value["surface_container_high"], theme), + surfaceContainerLow: parseColor(value["surface_container_low"], theme), + surfaceContainerLowest: + parseColor(value["surface_container_lowest"], theme), + surfaceDim: parseColor(value["surface_dim"], theme), + tertiaryFixedDim: parseColor(value["tertiary_fixed_dim"], theme), ); } TextTheme? parseTextTheme( - ThemeData theme, TextTheme textTheme, Map? j) { - if (j == null) { - return null; - } - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, TextTheme textTheme, + [TextTheme? defaultValue]) { + if (value == null) return defaultValue; return textTheme.copyWith( - bodyLarge: parseTextStyle("body_large"), - bodyMedium: parseTextStyle("body_medium"), - bodySmall: parseTextStyle("body_small"), - displayLarge: parseTextStyle("display_large"), - displayMedium: parseTextStyle("display_medium"), - displaySmall: parseTextStyle("display_small"), - headlineLarge: parseTextStyle("headline_large"), - headlineMedium: parseTextStyle("headline_medium"), - headlineSmall: parseTextStyle("headline_small"), - labelLarge: parseTextStyle("label_large"), - labelMedium: parseTextStyle("label_medium"), - labelSmall: parseTextStyle("label_small"), - titleLarge: parseTextStyle("title_large"), - titleMedium: parseTextStyle("title_medium"), - titleSmall: parseTextStyle("title_small"), + bodyLarge: parseTextStyle(value["body_large"], theme), + bodyMedium: parseTextStyle(value["body_medium"], theme), + bodySmall: parseTextStyle(value["body_small"], theme), + displayLarge: parseTextStyle(value["display_large"], theme), + displayMedium: parseTextStyle(value["display_medium"], theme), + displaySmall: parseTextStyle(value["display_small"], theme), + headlineLarge: parseTextStyle(value["headline_large"], theme), + headlineMedium: parseTextStyle(value["headline_medium"], theme), + headlineSmall: parseTextStyle(value["headline_small"], theme), + labelLarge: parseTextStyle(value["label_large"], theme), + labelMedium: parseTextStyle(value["label_medium"], theme), + labelSmall: parseTextStyle(value["label_small"], theme), + titleLarge: parseTextStyle(value["title_large"], theme), + titleMedium: parseTextStyle(value["title_medium"], theme), + titleSmall: parseTextStyle(value["title_small"], theme), ); } -ButtonThemeData? parseButtonTheme(ThemeData theme, Map? j) { - if (j == null) { - return null; - } +ButtonThemeData? parseButtonTheme(Map? value, ThemeData theme, + [ButtonThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.buttonTheme.copyWith( - buttonColor: parseColor(theme, j["button_color"]), - disabledColor: parseColor(theme, j["disabled_color"]), - hoverColor: parseColor(theme, j["hover_color"]), - focusColor: parseColor(theme, j["focus_color"]), - highlightColor: parseColor(theme, j["highlight_color"]), - splashColor: parseColor(theme, j["splash_color"]), - colorScheme: parseColorScheme(theme, j["color_scheme"]), - alignedDropdown: parseBool(j["aligned_dropdown"]), - height: parseDouble(j["height"]), - minWidth: parseDouble(j["min_width"]), - shape: outlinedBorderFromJSON(j["shape"]), - padding: edgeInsetsFromJson(j["padding"]), + buttonColor: parseColor(value["button_color"], theme), + disabledColor: parseColor(value["disabled_color"], theme), + hoverColor: parseColor(value["hover_color"], theme), + focusColor: parseColor(value["focus_color"], theme), + highlightColor: parseColor(value["highlight_color"], theme), + splashColor: parseColor(value["splash_color"], theme), + colorScheme: parseColorScheme(value["color_scheme"], theme), + alignedDropdown: parseBool(value["aligned_dropdown"]), + height: parseDouble(value["height"]), + minWidth: parseDouble(value["min_width"]), + shape: parseShape(value["shape"], theme), + padding: parsePadding(value["padding"]), ); } ElevatedButtonThemeData? parseElevatedButtonTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, + [ElevatedButtonThemeData? defaultValue]) { + if (value == null) return defaultValue; return ElevatedButtonThemeData( style: ElevatedButton.styleFrom( - iconColor: parseColor(theme, j["icon_color"]), - foregroundColor: parseColor(theme, j["foreground_color"]), - backgroundColor: parseColor(theme, j["bgcolor"]), - shadowColor: parseColor(theme, j["shadow_color"]), - disabledBackgroundColor: parseColor(theme, j["disabled_bgcolor"]), - disabledForegroundColor: parseColor(theme, j["disabled_foreground_color"]), - disabledIconColor: parseColor(theme, j["disabled_icon_color"]), - overlayColor: parseColor(theme, j["overlay_color"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - elevation: parseDouble(j["elevation"]), - padding: edgeInsetsFromJson(j["padding"]), - enableFeedback: parseBool(j["enable_feedback"]), - disabledMouseCursor: parseMouseCursor(j["disabled_mouse_cursor"]), - enabledMouseCursor: parseMouseCursor(j["enabled_mouse_cursor"]), - shape: outlinedBorderFromJSON(j["shape"]), - textStyle: parseTextStyle("text_style"), - visualDensity: parseVisualDensity(j["visual_density"]), - side: borderSideFromJSON(theme, j["border_side"]), - animationDuration: durationFromJSON(j["animation_duration"]), - alignment: alignmentFromJson(j["alignment"]), - iconSize: parseDouble(j["icon_size"]), - fixedSize: sizeFromJson(j["fixed_size"]), - maximumSize: sizeFromJson(j["maximum_size"]), - minimumSize: sizeFromJson(j["minimum_size"]), + iconColor: parseColor(value["icon_color"], theme), + foregroundColor: parseColor(value["foreground_color"], theme), + backgroundColor: parseColor(value["bgcolor"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + disabledBackgroundColor: parseColor(value["disabled_bgcolor"], theme), + disabledForegroundColor: + parseColor(value["disabled_foreground_color"], theme), + disabledIconColor: parseColor(value["disabled_icon_color"], theme), + overlayColor: parseColor(value["overlay_color"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + elevation: parseDouble(value["elevation"]), + padding: parsePadding(value["padding"]), + enableFeedback: parseBool(value["enable_feedback"]), + disabledMouseCursor: parseMouseCursor(value["disabled_mouse_cursor"]), + enabledMouseCursor: parseMouseCursor(value["enabled_mouse_cursor"]), + shape: parseShape(value["shape"], theme), + textStyle: parseTextStyle(value["text_style"], theme), + visualDensity: parseVisualDensity(value["visual_density"]), + side: parseBorderSide(value["border_side"], theme), + animationDuration: parseDuration(value["animation_duration"]), + alignment: parseAlignment(value["alignment"]), + iconSize: parseDouble(value["icon_size"]), + fixedSize: parseSize(value["fixed_size"]), + maximumSize: parseSize(value["maximum_size"]), + minimumSize: parseSize(value["minimum_size"]), )); } OutlinedButtonThemeData? parseOutlinedButtonTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, + [OutlinedButtonThemeData? defaultValue]) { + if (value == null) return defaultValue; return OutlinedButtonThemeData( style: OutlinedButton.styleFrom( - iconColor: parseColor(theme, j["icon_color"]), - foregroundColor: parseColor(theme, j["foreground_color"]), - backgroundColor: parseColor(theme, j["bgcolor"]), - shadowColor: parseColor(theme, j["shadow_color"]), - disabledBackgroundColor: parseColor(theme, j["disabled_bgcolor"]), - disabledForegroundColor: parseColor(theme, j["disabled_foreground_color"]), - disabledIconColor: parseColor(theme, j["disabled_icon_color"]), - overlayColor: parseColor(theme, j["overlay_color"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - elevation: parseDouble(j["elevation"]), - padding: edgeInsetsFromJson(j["padding"]), - enableFeedback: parseBool(j["enable_feedback"]), - disabledMouseCursor: parseMouseCursor(j["disabled_mouse_cursor"]), - enabledMouseCursor: parseMouseCursor(j["enabled_mouse_cursor"]), - shape: outlinedBorderFromJSON(j["shape"]), - textStyle: parseTextStyle("text_style"), - visualDensity: parseVisualDensity(j["visual_density"]), - side: borderSideFromJSON(theme, j["border_side"]), - animationDuration: durationFromJSON(j["animation_duration"]), - alignment: alignmentFromJson(j["alignment"]), - iconSize: parseDouble(j["icon_size"]), - fixedSize: sizeFromJson(j["fixed_size"]), - maximumSize: sizeFromJson(j["maximum_size"]), - minimumSize: sizeFromJson(j["minimum_size"]), + iconColor: parseColor(value["icon_color"], theme), + foregroundColor: parseColor(value["foreground_color"], theme), + backgroundColor: parseColor(value["bgcolor"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + disabledBackgroundColor: parseColor(value["disabled_bgcolor"], theme), + disabledForegroundColor: + parseColor(value["disabled_foreground_color"], theme), + disabledIconColor: parseColor(value["disabled_icon_color"], theme), + overlayColor: parseColor(value["overlay_color"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + elevation: parseDouble(value["elevation"]), + padding: parsePadding(value["padding"]), + enableFeedback: parseBool(value["enable_feedback"]), + disabledMouseCursor: parseMouseCursor(value["disabled_mouse_cursor"]), + enabledMouseCursor: parseMouseCursor(value["enabled_mouse_cursor"]), + shape: parseShape(value["shape"], theme), + textStyle: parseTextStyle(value["text_style"], theme), + visualDensity: parseVisualDensity(value["visual_density"]), + side: parseBorderSide(value["border_side"], theme), + animationDuration: parseDuration(value["animation_duration"]), + alignment: parseAlignment(value["alignment"]), + iconSize: parseDouble(value["icon_size"]), + fixedSize: parseSize(value["fixed_size"]), + maximumSize: parseSize(value["maximum_size"]), + minimumSize: parseSize(value["minimum_size"]), )); } TextButtonThemeData? parseTextButtonTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, + [TextButtonThemeData? defaultValue]) { + if (value == null) return defaultValue; return TextButtonThemeData( style: TextButton.styleFrom( - iconColor: parseColor(theme, j["icon_color"]), - foregroundColor: parseColor(theme, j["foreground_color"]), - backgroundColor: parseColor(theme, j["bgcolor"]), - shadowColor: parseColor(theme, j["shadow_color"]), - disabledBackgroundColor: parseColor(theme, j["disabled_bgcolor"]), - disabledForegroundColor: parseColor(theme, j["disabled_foreground_color"]), - disabledIconColor: parseColor(theme, j["disabled_icon_color"]), - overlayColor: parseColor(theme, j["overlay_color"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - elevation: parseDouble(j["elevation"]), - padding: edgeInsetsFromJson(j["padding"]), - enableFeedback: parseBool(j["enable_feedback"]), - disabledMouseCursor: parseMouseCursor(j["disabled_mouse_cursor"]), - enabledMouseCursor: parseMouseCursor(j["enabled_mouse_cursor"]), - shape: outlinedBorderFromJSON(j["shape"]), - textStyle: parseTextStyle("text_style"), - visualDensity: parseVisualDensity(j["visual_density"]), - side: borderSideFromJSON(theme, j["border_side"]), - animationDuration: durationFromJSON(j["animation_duration"]), - alignment: alignmentFromJson(j["alignment"]), - iconSize: parseDouble(j["icon_size"]), - fixedSize: sizeFromJson(j["fixed_size"]), - maximumSize: sizeFromJson(j["maximum_size"]), - minimumSize: sizeFromJson(j["minimum_size"]), + iconColor: parseColor(value["icon_color"], theme), + foregroundColor: parseColor(value["foreground_color"], theme), + backgroundColor: parseColor(value["bgcolor"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + disabledBackgroundColor: parseColor(value["disabled_bgcolor"], theme), + disabledForegroundColor: + parseColor(value["disabled_foreground_color"], theme), + disabledIconColor: parseColor(value["disabled_icon_color"], theme), + overlayColor: parseColor(value["overlay_color"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + elevation: parseDouble(value["elevation"]), + padding: parsePadding(value["padding"]), + enableFeedback: parseBool(value["enable_feedback"]), + disabledMouseCursor: parseMouseCursor(value["disabled_mouse_cursor"]), + enabledMouseCursor: parseMouseCursor(value["enabled_mouse_cursor"]), + shape: parseShape(value["shape"], theme), + textStyle: parseTextStyle(value["text_style"], theme), + visualDensity: parseVisualDensity(value["visual_density"]), + side: parseBorderSide(value["border_side"], theme), + animationDuration: parseDuration(value["animation_duration"]), + alignment: parseAlignment(value["alignment"]), + iconSize: parseDouble(value["icon_size"]), + fixedSize: parseSize(value["fixed_size"]), + maximumSize: parseSize(value["maximum_size"]), + minimumSize: parseSize(value["minimum_size"]), )); } FilledButtonThemeData? parseFilledButtonTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, + [FilledButtonThemeData? defaultValue]) { + if (value == null) return defaultValue; return FilledButtonThemeData( style: FilledButton.styleFrom( - iconColor: parseColor(theme, j["icon_color"]), - foregroundColor: parseColor(theme, j["foreground_color"]), - backgroundColor: parseColor(theme, j["bgcolor"]), - shadowColor: parseColor(theme, j["shadow_color"]), - disabledBackgroundColor: parseColor(theme, j["disabled_bgcolor"]), - disabledForegroundColor: parseColor(theme, j["disabled_foreground_color"]), - disabledIconColor: parseColor(theme, j["disabled_icon_color"]), - overlayColor: parseColor(theme, j["overlay_color"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - elevation: parseDouble(j["elevation"]), - padding: edgeInsetsFromJson(j["padding"]), - enableFeedback: parseBool(j["enable_feedback"]), - disabledMouseCursor: parseMouseCursor(j["disabled_mouse_cursor"]), - enabledMouseCursor: parseMouseCursor(j["enabled_mouse_cursor"]), - shape: outlinedBorderFromJSON(j["shape"]), - textStyle: parseTextStyle("text_style"), - visualDensity: parseVisualDensity(j["visual_density"]), - side: borderSideFromJSON(theme, j["border_side"]), - animationDuration: durationFromJSON(j["animation_duration"]), - alignment: alignmentFromJson(j["alignment"]), - iconSize: parseDouble(j["icon_size"]), - fixedSize: sizeFromJson(j["fixed_size"]), - maximumSize: sizeFromJson(j["maximum_size"]), - minimumSize: sizeFromJson(j["minimum_size"]), + iconColor: parseColor(value["icon_color"], theme), + foregroundColor: parseColor(value["foreground_color"], theme), + backgroundColor: parseColor(value["bgcolor"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + disabledBackgroundColor: parseColor(value["disabled_bgcolor"], theme), + disabledForegroundColor: + parseColor(value["disabled_foreground_color"], theme), + disabledIconColor: parseColor(value["disabled_icon_color"], theme), + overlayColor: parseColor(value["overlay_color"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + elevation: parseDouble(value["elevation"]), + padding: parsePadding(value["padding"]), + enableFeedback: parseBool(value["enable_feedback"]), + disabledMouseCursor: parseMouseCursor(value["disabled_mouse_cursor"]), + enabledMouseCursor: parseMouseCursor(value["enabled_mouse_cursor"]), + shape: parseShape(value["shape"], theme), + textStyle: parseTextStyle(value["text_style"], theme), + visualDensity: parseVisualDensity(value["visual_density"]), + side: parseBorderSide(value["border_side"], theme), + animationDuration: parseDuration(value["animation_duration"]), + alignment: parseAlignment(value["alignment"]), + iconSize: parseDouble(value["icon_size"]), + fixedSize: parseSize(value["fixed_size"]), + maximumSize: parseSize(value["maximum_size"]), + minimumSize: parseSize(value["minimum_size"]), )); } IconButtonThemeData? parseIconButtonTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, + [IconButtonThemeData? defaultValue]) { + if (value == null) return defaultValue; return IconButtonThemeData( style: IconButton.styleFrom( - foregroundColor: parseColor(theme, j["foreground_color"]), - backgroundColor: parseColor(theme, j["bgcolor"]), - shadowColor: parseColor(theme, j["shadow_color"]), - disabledBackgroundColor: parseColor(theme, j["disabled_bgcolor"]), - disabledForegroundColor: parseColor(theme, j["disabled_foreground_color"]), - overlayColor: parseColor(theme, j["overlay_color"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - focusColor: parseColor(theme, j["focus_color"]), - highlightColor: parseColor(theme, j["highlight_color"]), - hoverColor: parseColor(theme, j["hover_color"]), - elevation: parseDouble(j["elevation"]), - padding: edgeInsetsFromJson(j["padding"]), - enableFeedback: parseBool(j["enable_feedback"]), - disabledMouseCursor: parseMouseCursor(j["disabled_mouse_cursor"]), - enabledMouseCursor: parseMouseCursor(j["enabled_mouse_cursor"]), - shape: outlinedBorderFromJSON(j["shape"]), - visualDensity: parseVisualDensity(j["visual_density"]), - side: borderSideFromJSON(theme, j["border_side"]), - animationDuration: durationFromJSON(j["animation_duration"]), - alignment: alignmentFromJson(j["alignment"]), - iconSize: parseDouble(j["icon_size"]), - fixedSize: sizeFromJson(j["fixed_size"]), - maximumSize: sizeFromJson(j["maximum_size"]), - minimumSize: sizeFromJson(j["minimum_size"]), + foregroundColor: parseColor(value["foreground_color"], theme), + backgroundColor: parseColor(value["bgcolor"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + disabledBackgroundColor: parseColor(value["disabled_bgcolor"], theme), + disabledForegroundColor: + parseColor(value["disabled_foreground_color"], theme), + overlayColor: parseColor(value["overlay_color"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + focusColor: parseColor(value["focus_color"], theme), + highlightColor: parseColor(value["highlight_color"], theme), + hoverColor: parseColor(value["hover_color"], theme), + elevation: parseDouble(value["elevation"]), + padding: parsePadding(value["padding"]), + enableFeedback: parseBool(value["enable_feedback"]), + disabledMouseCursor: parseMouseCursor(value["disabled_mouse_cursor"]), + enabledMouseCursor: parseMouseCursor(value["enabled_mouse_cursor"]), + shape: parseShape(value["shape"], theme), + visualDensity: parseVisualDensity(value["visual_density"]), + side: parseBorderSide(value["border_side"], theme), + animationDuration: parseDuration(value["animation_duration"]), + alignment: parseAlignment(value["alignment"]), + iconSize: parseDouble(value["icon_size"]), + fixedSize: parseSize(value["fixed_size"]), + maximumSize: parseSize(value["maximum_size"]), + minimumSize: parseSize(value["minimum_size"]), )); } DataTableThemeData? parseDataTableTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, BuildContext context, + [DataTableThemeData? defaultValue]) { + if (value == null) return defaultValue; + var theme = Theme.of(context); return theme.dataTableTheme.copyWith( - checkboxHorizontalMargin: parseDouble(j["checkbox_horizontal_margin"]), - columnSpacing: parseDouble(j["column_spacing"]), - dataRowMaxHeight: parseDouble(j["data_row_max_height"]), - dataRowMinHeight: parseDouble(j["data_row_min_height"]), - dataRowColor: getWidgetStateProperty( - j["data_row_color"], (jv) => parseColor(theme, jv as String)), - dataTextStyle: parseTextStyle("data_text_style"), - dividerThickness: parseDouble(j["divider_thickness"]), - horizontalMargin: parseDouble(j["horizontal_margin"]), - headingTextStyle: parseTextStyle("heading_text_style"), - headingRowColor: getWidgetStateProperty( - j["heading_row_color"], (jv) => parseColor(theme, jv as String)), - headingRowHeight: parseDouble(j["heading_row_height"]), - dataRowCursor: getWidgetStateProperty( - j["data_row_cursor"], (jv) => parseMouseCursor(jv)), - decoration: boxDecorationFromJSON(theme, j["decoration"], null), - // TODO: replace null with a proper PageArgsModel - headingRowAlignment: parseMainAxisAlignment(j["heading_row_alignment"]), - headingCellCursor: getWidgetStateProperty( - j["heading_cell_cursor"], (jv) => parseMouseCursor(jv)), + checkboxHorizontalMargin: parseDouble(value["checkbox_horizontal_margin"]), + columnSpacing: parseDouble(value["column_spacing"]), + dataRowMaxHeight: parseDouble(value["data_row_max_height"]), + dataRowMinHeight: parseDouble(value["data_row_min_height"]), + dataRowColor: parseWidgetStateColor(value["data_row_color"], theme), + dataTextStyle: parseTextStyle(value["data_text_style"], theme), + dividerThickness: parseDouble(value["divider_thickness"]), + horizontalMargin: parseDouble(value["horizontal_margin"]), + headingTextStyle: parseTextStyle(value["heading_text_style"], theme), + headingRowColor: parseWidgetStateColor(value["heading_row_color"], theme), + headingRowHeight: parseDouble(value["heading_row_height"]), + dataRowCursor: parseWidgetStateMouseCursor(value["data_row_cursor"]), + decoration: parseBoxDecoration(value["decoration"], context), + headingRowAlignment: parseMainAxisAlignment(value["heading_row_alignment"]), + headingCellCursor: + parseWidgetStateMouseCursor(value["heading_cell_cursor"]), ); } ScrollbarThemeData? parseScrollBarTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } + Map? value, ThemeData theme, + [ScrollbarThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.scrollbarTheme.copyWith( - trackVisibility: getWidgetStateProperty( - j["track_visibility"], (jv) => parseBool(jv)), - trackColor: getWidgetStateProperty( - j["track_color"], (jv) => parseColor(theme, jv as String)), - trackBorderColor: getWidgetStateProperty( - j["track_border_color"], (jv) => parseColor(theme, jv as String)), - thumbVisibility: getWidgetStateProperty( - j["thumb_visibility"], (jv) => parseBool(jv)), - thumbColor: getWidgetStateProperty( - j["thumb_color"], (jv) => parseColor(theme, jv as String)), - thickness: getWidgetStateProperty( - j["thickness"], (jv) => parseDouble(jv, 0)!), - radius: j["radius"] != null - ? Radius.circular(parseDouble(j["radius"], 0)!) - : null, - crossAxisMargin: parseDouble(j["cross_axis_margin"]), - mainAxisMargin: parseDouble(j["main_axis_margin"]), - minThumbLength: parseDouble(j["min_thumb_length"]), - interactive: parseBool(j["interactive"]), + trackVisibility: parseWidgetStateBool(value["track_visibility"]), + trackColor: parseWidgetStateColor(value["track_color"], theme), + trackBorderColor: parseWidgetStateColor(value["track_border_color"], theme), + thumbVisibility: parseWidgetStateBool(value["thumb_visibility"]), + thumbColor: parseWidgetStateColor(value["thumb_color"], theme), + thickness: parseWidgetStateDouble(value["thickness"]), + radius: parseRadius(value["radius"]), + crossAxisMargin: parseDouble(value["cross_axis_margin"]), + mainAxisMargin: parseDouble(value["main_axis_margin"]), + minThumbLength: parseDouble(value["min_thumb_length"]), + interactive: parseBool(value["interactive"]), ); } -TabBarThemeData? parseTabBarTheme(ThemeData theme, Map? j) { - if (j == null) { - return null; - } +TabBarThemeData? parseTabBarTheme(Map? value, ThemeData theme, + [TabBarThemeData? defaultValue]) { + if (value == null) return defaultValue; - var indicatorColor = parseColor(theme, j["indicator_color"]); + var indicatorColor = + parseColor(value["indicator_color"], theme, theme.colorScheme.primary)!; return theme.tabBarTheme.copyWith( - overlayColor: getWidgetStateProperty( - j["overlay_color"], (jv) => parseColor(theme, jv as String)), - dividerColor: parseColor(theme, j["divider_color"]), - indicatorColor: indicatorColor, - labelColor: parseColor(theme, j["label_color"]), - unselectedLabelColor: parseColor(theme, j["unselected_label_color"]), - indicatorSize: parseBool(j["indicator_tab_size"], false)! - ? TabBarIndicatorSize.tab - : TabBarIndicatorSize.label, - indicator: j["indicator_border_radius"] != null || - j["indicator_border_side"] != null || - j["indicator_padding"] != null - ? UnderlineTabIndicator( - borderRadius: borderRadiusFromJSON( - j["indicator_border_radius"], - const BorderRadius.only( - topLeft: Radius.circular(2), - topRight: Radius.circular(2)))!, - borderSide: borderSideFromJSON( - theme, j["indicator_border_side"], indicatorColor) ?? - BorderSide( - width: 2.0, - color: indicatorColor ?? theme.colorScheme.primary), - insets: - edgeInsetsFromJson(j["indicator_padding"]) ?? EdgeInsets.zero) - : null, - mouseCursor: getWidgetStateProperty( - j["mouse_cursor"], (jv) => parseMouseCursor(jv)), - labelPadding: edgeInsetsFromJson(j["label_padding"]), - dividerHeight: parseDouble(j["divider_height"]), - labelStyle: j["label_text_style"] != null - ? textStyleFromJson(theme, j["label_text_style"]) - : null, - unselectedLabelStyle: j["unselected_label_text_style"] != null - ? textStyleFromJson(theme, j["unselected_label_text_style"]) - : null, - ); + overlayColor: parseWidgetStateColor(value["overlay_color"], theme), + dividerColor: parseColor(value["divider_color"], theme), + indicatorColor: indicatorColor, + labelColor: parseColor(value["label_color"], theme), + unselectedLabelColor: parseColor(value["unselected_label_color"], theme), + indicatorSize: parseBool(value["indicator_tab_size"], false)! + ? TabBarIndicatorSize.tab + : TabBarIndicatorSize.label, + indicator: value["indicator_border_radius"] != null || + value["indicator_border_side"] != null || + value["indicator_padding"] != null + ? UnderlineTabIndicator( + borderRadius: parseBorderRadius( + value["indicator_border_radius"], + const BorderRadius.only( + topLeft: Radius.circular(2), + topRight: Radius.circular(2)))!, + borderSide: parseBorderSide(value["indicator_border_side"], theme, + defaultSideColor: indicatorColor, + defaultValue: BorderSide(width: 2.0, color: indicatorColor))!, + insets: + parsePadding(value["indicator_padding"], EdgeInsets.zero)!) + : null, + mouseCursor: parseWidgetStateMouseCursor(value["mouse_cursor"]), + labelPadding: parsePadding(value["label_padding"]), + dividerHeight: parseDouble(value["divider_height"]), + labelStyle: parseTextStyle(value["label_text_style"], theme), + unselectedLabelStyle: + parseTextStyle(value["unselected_label_text_style"], theme)); } -VisualDensity? parseVisualDensity(String? density, [VisualDensity? defValue]) { +VisualDensity? parseVisualDensity(String? density, + [VisualDensity? defaultValue]) { switch (density?.toLowerCase()) { case "adaptiveplatformdensity": return VisualDensity.adaptivePlatformDensity; @@ -616,883 +575,723 @@ VisualDensity? parseVisualDensity(String? density, [VisualDensity? defValue]) { case "standard": return VisualDensity.standard; default: - return defValue; + return defaultValue; } } -PageTransitionsTheme? parsePageTransitions(Map? json, - [PageTransitionsTheme? defValue]) { - if (json == null) { - return defValue; +PageTransitionsTheme? parsePageTransitions(Map? value, + [PageTransitionsTheme? defaultValue]) { + if (value == null) { + return defaultValue; } return PageTransitionsTheme(builders: { TargetPlatform.android: parseTransitionsBuilder( - json["android"], const FadeUpwardsPageTransitionsBuilder()), + value["android"], const FadeUpwardsPageTransitionsBuilder())!, TargetPlatform.iOS: parseTransitionsBuilder( - json["ios"], const CupertinoPageTransitionsBuilder()), + value["ios"], const CupertinoPageTransitionsBuilder())!, TargetPlatform.linux: parseTransitionsBuilder( - json["linux"], const ZoomPageTransitionsBuilder()), + value["linux"], const ZoomPageTransitionsBuilder())!, TargetPlatform.macOS: parseTransitionsBuilder( - json["macos"], const ZoomPageTransitionsBuilder()), + value["macos"], const ZoomPageTransitionsBuilder())!, TargetPlatform.windows: parseTransitionsBuilder( - json["windows"], const ZoomPageTransitionsBuilder()), + value["windows"], const ZoomPageTransitionsBuilder())!, }); } -DialogThemeData? parseDialogTheme(ThemeData theme, Map? j) { - if (j == null) { - return null; - } - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } +DialogThemeData? parseDialogTheme(Map? value, ThemeData theme, + [DialogThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.dialogTheme.copyWith( - backgroundColor: parseColor(theme, j["bgcolor"]), - shadowColor: parseColor(theme, j["shadow_color"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - iconColor: parseColor(theme, j["icon_color"]), - elevation: parseDouble(j["elevation"]), - shape: j["shape"] != null ? outlinedBorderFromJSON(j["shape"]) : null, - titleTextStyle: parseTextStyle("title_text_style"), - contentTextStyle: parseTextStyle("content_text_style"), - alignment: alignmentFromJson(j["alignment"]), - actionsPadding: edgeInsetsFromJson(j["actions_padding"]), - clipBehavior: parseClip(j["clip_behavior"]), - barrierColor: parseColor(theme, j["barrier_color"]), - insetPadding: edgeInsetsFromJson(j["inset_padding"]), + backgroundColor: parseColor(value["bgcolor"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + iconColor: parseColor(value["icon_color"], theme), + elevation: parseDouble(value["elevation"]), + shape: parseShape(value["shape"], theme), + titleTextStyle: parseTextStyle(value["title_text_style"], theme), + contentTextStyle: parseTextStyle(value["content_text_style"], theme), + alignment: parseAlignment(value["alignment"]), + actionsPadding: parsePadding(value["actions_padding"]), + clipBehavior: parseClip(value["clip_behavior"]), + barrierColor: parseColor(value["barrier_color"], theme), + insetPadding: parsePadding(value["inset_padding"]), ); } BottomSheetThemeData? parseBottomSheetTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } + Map? value, ThemeData theme, + [BottomSheetThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.bottomSheetTheme.copyWith( - backgroundColor: parseColor(theme, j["bgcolor"]), - shadowColor: parseColor(theme, j["shadow_color"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - dragHandleColor: parseColor(theme, j["drag_handle_color"]), - elevation: parseDouble(j["elevation"]), - shape: j["shape"] != null ? outlinedBorderFromJSON(j["shape"]) : null, - showDragHandle: parseBool(j["show_drag_handle"]), - modalBackgroundColor: parseColor(theme, j["modal_bgcolor"]), - modalElevation: parseDouble(j["modal_elevation"]), - clipBehavior: parseClip(j["clip_behavior"]), - constraints: boxConstraintsFromJSON(j["size_constraints"]), - modalBarrierColor: parseColor(theme, j["modal_barrier_color"]), + backgroundColor: parseColor(value["bgcolor"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + dragHandleColor: parseColor(value["drag_handle_color"], theme), + elevation: parseDouble(value["elevation"]), + shape: parseShape(value["shape"], theme), + showDragHandle: parseBool(value["show_drag_handle"]), + modalBackgroundColor: parseColor(value["modal_bgcolor"], theme), + modalElevation: parseDouble(value["modal_elevation"]), + clipBehavior: parseClip(value["clip_behavior"]), + constraints: parseBoxConstraints(value["size_constraints"]), + modalBarrierColor: parseColor(value["modal_barrier_color"], theme), ); } -CardThemeData? parseCardTheme(ThemeData theme, Map? j) { - if (j == null) { - return null; - } +CardThemeData? parseCardTheme(Map? value, ThemeData theme, + [CardThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.cardTheme.copyWith( - color: parseColor(theme, j["color"]), - shadowColor: parseColor(theme, j["shadow_color"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - elevation: parseDouble(j["elevation"]), - shape: j["shape"] != null ? outlinedBorderFromJSON(j["shape"]) : null, - clipBehavior: parseClip(j["clip_behavior"]), - margin: edgeInsetsFromJson(j["margin"])); + color: parseColor(value["color"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + elevation: parseDouble(value["elevation"]), + shape: parseShape(value["shape"], theme), + clipBehavior: parseClip(value["clip_behavior"]), + margin: parseMargin(value["margin"])); } -ChipThemeData? parseChipTheme(ThemeData theme, Map? j) { - if (j == null) { - return null; - } - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } +ChipThemeData? parseChipTheme(Map? value, ThemeData theme, + [ChipThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.chipTheme.copyWith( - color: getWidgetStateProperty( - j["color"], (jv) => parseColor(theme, jv as String)), - backgroundColor: parseColor(theme, j["bgcolor"]), - shadowColor: parseColor(theme, j["shadow_color"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - elevation: parseDouble(j["elevation"]), - shape: j["shape"] != null ? outlinedBorderFromJSON(j["shape"]) : null, - padding: edgeInsetsFromJson(j["padding"]), - labelPadding: edgeInsetsFromJson(j["label_padding"]), - labelStyle: parseTextStyle("label_text_style"), - secondaryLabelStyle: parseTextStyle("secondary_label_text_style"), - disabledColor: parseColor(theme, j["disabled_color"]), - selectedColor: parseColor(theme, j["selected_color"]), - checkmarkColor: parseColor(theme, j["check_color"]), - deleteIconColor: parseColor(theme, j["delete_icon_color"]), - side: j["border_side"] != null - ? borderSideFromJSON(theme, j["border_side"], null) - : null, - secondarySelectedColor: parseColor(theme, j["secondary_selected_color"]), - brightness: j["brightness"] != null - ? Brightness.values.firstWhereOrNull( - (b) => b.name.toLowerCase() == j["brightness"].toLowerCase()) - : null, - selectedShadowColor: parseColor(theme, j["selected_shadow_color"]), - showCheckmark: parseBool(j["show_checkmark"]), - pressElevation: parseDouble(j["click_elevation"]), - avatarBoxConstraints: boxConstraintsFromJSON(j["avatar_constraints"]), + color: parseWidgetStateColor(value["color"], theme), + backgroundColor: parseColor(value["bgcolor"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + elevation: parseDouble(value["elevation"]), + shape: parseShape(value["shape"], theme), + padding: parsePadding(value["padding"]), + labelPadding: parsePadding(value["label_padding"]), + labelStyle: parseTextStyle(value["label_text_style"], theme), + secondaryLabelStyle: + parseTextStyle(value["secondary_label_text_style"], theme), + disabledColor: parseColor(value["disabled_color"], theme), + selectedColor: parseColor(value["selected_color"], theme), + checkmarkColor: parseColor(value["check_color"], theme), + deleteIconColor: parseColor(value["delete_icon_color"], theme), + side: parseBorderSide(value["border_side"], theme), + secondarySelectedColor: + parseColor(value["secondary_selected_color"], theme), + brightness: parseBrightness(value["brightness"]), + selectedShadowColor: parseColor(value["selected_shadow_color"], theme), + showCheckmark: parseBool(value["show_checkmark"]), + pressElevation: parseDouble(value["click_elevation"]), + avatarBoxConstraints: parseBoxConstraints(value["avatar_constraints"]), deleteIconBoxConstraints: - boxConstraintsFromJSON(j["delete_icon_size_constraints"]), + parseBoxConstraints(value["delete_icon_size_constraints"]), ); } FloatingActionButtonThemeData? parseFloatingActionButtonTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, + [FloatingActionButtonThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.floatingActionButtonTheme.copyWith( - backgroundColor: parseColor(theme, j["bgcolor"]), - hoverColor: parseColor(theme, j["hover_color"]), - focusColor: parseColor(theme, j["focus_color"]), - foregroundColor: parseColor(theme, j["foreground_color"]), - splashColor: parseColor(theme, j["splash_color"]), - elevation: parseDouble(j["elevation"]), - focusElevation: parseDouble(j["focus_elevation"]), - hoverElevation: parseDouble(j["hover_elevation"]), - highlightElevation: parseDouble(j["highlight_elevation"]), - disabledElevation: parseDouble(j["disabled_elevation"]), - shape: j["shape"] != null ? outlinedBorderFromJSON(j["shape"]) : null, - enableFeedback: parseBool(j["enable_feedback"]), - extendedPadding: edgeInsetsFromJson(j["extended_padding"]), - extendedTextStyle: parseTextStyle("extended_text_style"), - extendedIconLabelSpacing: parseDouble(j["extended_icon_label_spacing"]), - mouseCursor: getWidgetStateProperty( - j["mouse_cursor"], (jv) => parseMouseCursor(jv)), - iconSize: parseDouble(j["icon_size"]), + backgroundColor: parseColor(value["bgcolor"], theme), + hoverColor: parseColor(value["hover_color"], theme), + focusColor: parseColor(value["focus_color"], theme), + foregroundColor: parseColor(value["foreground_color"], theme), + splashColor: parseColor(value["splash_color"], theme), + elevation: parseDouble(value["elevation"]), + focusElevation: parseDouble(value["focus_elevation"]), + hoverElevation: parseDouble(value["hover_elevation"]), + highlightElevation: parseDouble(value["highlight_elevation"]), + disabledElevation: parseDouble(value["disabled_elevation"]), + shape: parseShape(value["shape"], theme), + enableFeedback: parseBool(value["enable_feedback"]), + extendedPadding: parsePadding(value["extended_padding"]), + extendedTextStyle: parseTextStyle(value["extended_text_style"], theme), + extendedIconLabelSpacing: parseDouble(value["extended_icon_label_spacing"]), + mouseCursor: parseWidgetStateMouseCursor(value["mouse_cursor"]), + iconSize: parseDouble(value["icon_size"]), extendedSizeConstraints: - boxConstraintsFromJSON(j["extended_size_constraints"]), - sizeConstraints: boxConstraintsFromJSON(j["size_constraints"]), - smallSizeConstraints: boxConstraintsFromJSON(j["small_size_constraints"]), - largeSizeConstraints: boxConstraintsFromJSON(j["large_size_constraints"]), + parseBoxConstraints(value["extended_size_constraints"]), + sizeConstraints: parseBoxConstraints(value["size_constraints"]), + smallSizeConstraints: parseBoxConstraints(value["small_size_constraints"]), + largeSizeConstraints: parseBoxConstraints(value["large_size_constraints"]), ); } NavigationRailThemeData? parseNavigationRailTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, + [NavigationRailThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.navigationRailTheme.copyWith( - backgroundColor: parseColor(theme, j["bgcolor"]), - elevation: parseDouble(j["elevation"]), - indicatorColor: parseColor(theme, j["indicator_color"]), - unselectedLabelTextStyle: parseTextStyle("unselected_label_text_style"), - selectedLabelTextStyle: parseTextStyle("selected_label_text_style"), - minWidth: parseDouble(j["min_width"]), - labelType: j["label_type"] != null - ? NavigationRailLabelType.values - .firstWhereOrNull((c) => c.name == j["label_type"]) - : null, - groupAlignment: parseDouble(j["group_alignment"]), - indicatorShape: j["indicator_shape"] != null - ? outlinedBorderFromJSON(j["indicator_shape"]) - : null, - minExtendedWidth: parseDouble(j["min_extended_width"]), - useIndicator: parseBool(j["use_indicator"]), + backgroundColor: parseColor(value["bgcolor"], theme), + elevation: parseDouble(value["elevation"]), + indicatorColor: parseColor(value["indicator_color"], theme), + unselectedLabelTextStyle: + parseTextStyle(value["unselected_label_text_style"], theme), + selectedLabelTextStyle: + parseTextStyle(value["selected_label_text_style"], theme), + minWidth: parseDouble(value["min_width"]), + labelType: parseNavigationRailLabelType(value["label_type"]), + groupAlignment: parseDouble(value["group_alignment"]), + indicatorShape: parseShape(value["indicator_shape"], theme), + minExtendedWidth: parseDouble(value["min_extended_width"]), + useIndicator: parseBool(value["use_indicator"]), ); } -AppBarTheme? parseAppBarTheme(ThemeData theme, Map? j) { - if (j == null) { - return null; - } - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } +AppBarTheme? parseAppBarTheme(Map? value, ThemeData theme, + [AppBarTheme? defaultValue]) { + if (value == null) return defaultValue; return theme.appBarTheme.copyWith( - backgroundColor: parseColor(theme, j["bgcolor"]), - color: parseColor(theme, j["color"]), - shadowColor: parseColor(theme, j["shadow_color"]), - foregroundColor: parseColor(theme, j["foreground_color"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - titleTextStyle: parseTextStyle("title_text_style"), - toolbarTextStyle: parseTextStyle("toolbar_text_style"), - shape: j["shape"] != null ? outlinedBorderFromJSON(j["shape"]) : null, - elevation: parseDouble(j["elevation"]), - centerTitle: parseBool(j["center_title"]), - titleSpacing: parseDouble(j["title_spacing"]), - scrolledUnderElevation: parseDouble(j["scroll_elevation"]), - toolbarHeight: parseDouble(j["toolbar_height"]), - actionsPadding: edgeInsetsFromJson(j["actions_padding"]), + backgroundColor: parseColor(value["bgcolor"], theme), + color: parseColor(value["color"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + foregroundColor: parseColor(value["foreground_color"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + titleTextStyle: parseTextStyle(value["title_text_style"], theme), + toolbarTextStyle: parseTextStyle(value["toolbar_text_style"], theme), + shape: parseShape(value["shape"], theme), + elevation: parseDouble(value["elevation"]), + centerTitle: parseBool(value["center_title"]), + titleSpacing: parseDouble(value["title_spacing"]), + scrolledUnderElevation: parseDouble(value["scroll_elevation"]), + toolbarHeight: parseDouble(value["toolbar_height"]), + actionsPadding: parsePadding(value["actions_padding"]), ); } BottomAppBarTheme? parseBottomAppBarTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } + Map? value, ThemeData theme, + [BottomAppBarTheme? defaultValue]) { + if (value == null) return defaultValue; return theme.bottomAppBarTheme.copyWith( - color: parseColor(theme, j["color"]), - shadowColor: parseColor(theme, j["shadow_color"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - elevation: parseDouble(j["elevation"]), - height: parseDouble(j["height"]), - padding: edgeInsetsFromJson(j["padding"]), - shape: parseNotchedShape(j["shape"]), + color: parseColor(value["color"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + elevation: parseDouble(value["elevation"]), + height: parseDouble(value["height"]), + padding: parsePadding(value["padding"]), + shape: parseNotchedShape(value["shape"]), ); } -RadioThemeData? parseRadioTheme(ThemeData theme, Map? j) { - if (j == null) { - return null; - } +RadioThemeData? parseRadioTheme(Map? value, ThemeData theme, + [RadioThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.radioTheme.copyWith( - fillColor: getWidgetStateProperty( - j["fill_color"], (jv) => parseColor(theme, jv as String)), - splashRadius: parseDouble(j["splash_radius"]), - overlayColor: getWidgetStateProperty( - j["overlay_color"], (jv) => parseColor(theme, jv as String)), - visualDensity: parseVisualDensity(j["visual_density"]), - mouseCursor: getWidgetStateProperty( - j["mouse_cursor"], (jv) => parseMouseCursor(jv)), + fillColor: parseWidgetStateColor(value["fill_color"], theme), + splashRadius: parseDouble(value["splash_radius"]), + overlayColor: parseWidgetStateColor(value["overlay_color"], theme), + visualDensity: parseVisualDensity(value["visual_density"]), + mouseCursor: parseWidgetStateMouseCursor(value["mouse_cursor"]), ); } CheckboxThemeData? parseCheckboxTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } + Map? value, ThemeData theme, + [CheckboxThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.checkboxTheme.copyWith( - fillColor: getWidgetStateProperty( - j["fill_color"], (jv) => parseColor(theme, jv as String)), - splashRadius: parseDouble(j["splash_radius"]), - overlayColor: getWidgetStateProperty( - j["overlay_color"], (jv) => parseColor(theme, jv as String)), - visualDensity: parseVisualDensity(j["visual_density"]), - checkColor: getWidgetStateProperty( - j["check_color"], (jv) => parseColor(theme, jv as String)), - side: j["border_side"] != null - ? borderSideFromJSON(theme, j["border_side"], null) - : null, - shape: j["shape"] != null ? outlinedBorderFromJSON(j["shape"]) : null, - mouseCursor: getWidgetStateProperty( - j["mouse_cursor"], (jv) => parseMouseCursor(jv)), + fillColor: parseWidgetStateColor(value["fill_color"], theme), + splashRadius: parseDouble(value["splash_radius"]), + overlayColor: parseWidgetStateColor(value["overlay_color"], theme), + visualDensity: parseVisualDensity(value["visual_density"]), + checkColor: parseWidgetStateColor(value["check_color"], theme), + side: parseBorderSide(value["border_side"], theme), + shape: parseShape(value["shape"], theme), + mouseCursor: parseWidgetStateMouseCursor(value["mouse_cursor"]), ); } -BadgeThemeData? parseBadgeTheme(ThemeData theme, Map? j) { - if (j == null) { - return null; - } - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } +BadgeThemeData? parseBadgeTheme(Map? value, ThemeData theme, + [BadgeThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.badgeTheme.copyWith( - backgroundColor: parseColor(theme, j["bgcolor"]), - textStyle: parseTextStyle("text_style"), - padding: edgeInsetsFromJson(j["padding"]), - alignment: alignmentFromJson(j["alignment"]), - textColor: parseColor(theme, j["text_color"]), - offset: j["offset"] != null ? offsetFromJson(j["offset"]) : null, - smallSize: parseDouble(j["small_size"]), - largeSize: parseDouble(j["large_size"]), + backgroundColor: parseColor(value["bgcolor"], theme), + textStyle: parseTextStyle(value["text_style"], theme), + padding: parsePadding(value["padding"]), + alignment: parseAlignment(value["alignment"]), + textColor: parseColor(value["text_color"], theme), + offset: parseOffset(value["offset"]), + smallSize: parseDouble(value["small_size"]), + largeSize: parseDouble(value["large_size"]), ); } -SwitchThemeData? parseSwitchTheme(ThemeData theme, Map? j) { - if (j == null) { - return null; - } +SwitchThemeData? parseSwitchTheme(Map? value, ThemeData theme, + [SwitchThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.switchTheme.copyWith( - thumbColor: getWidgetStateProperty( - j["thumb_color"], (jv) => parseColor(theme, jv as String)), - trackColor: getWidgetStateProperty( - j["track_color"], (jv) => parseColor(theme, jv as String)), - overlayColor: getWidgetStateProperty( - j["overlay_color"], (jv) => parseColor(theme, jv as String)), - splashRadius: - j["splash_radius"] != null ? parseDouble(j["splash_radius"]) : null, - thumbIcon: getWidgetStateProperty( - j["thumb_icon"], (jv) => Icon(parseIcon(jv as String))), - trackOutlineColor: getWidgetStateProperty(j["track_outline_color"], - (jv) => parseColor(theme, jv as String), null), - trackOutlineWidth: getWidgetStateProperty( - j["track_outline_width"], (jv) => parseDouble(jv)), - mouseCursor: getWidgetStateProperty( - j["mouse_cursor"], (jv) => parseMouseCursor(jv)), - padding: edgeInsetsFromJson(j["padding"]), + thumbColor: parseWidgetStateColor(value["thumb_color"], theme), + trackColor: parseWidgetStateColor(value["track_color"], theme), + overlayColor: parseWidgetStateColor(value["overlay_color"], theme), + splashRadius: parseDouble(value["splash_radius"]), + thumbIcon: parseWidgetStateIcon(value["thumb_icon"], theme), + trackOutlineColor: + parseWidgetStateColor(value["track_outline_color"], theme), + trackOutlineWidth: parseWidgetStateDouble(value["track_outline_width"]), + mouseCursor: parseWidgetStateMouseCursor(value["mouse_cursor"]), + padding: parsePadding(value["padding"]), ); } -DividerThemeData? parseDividerTheme(ThemeData theme, Map? j) { - if (j == null) { - return null; - } +DividerThemeData? parseDividerTheme( + Map? value, ThemeData theme, + [DividerThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.dividerTheme.copyWith( - color: parseColor(theme, j["color"]), - space: parseDouble(j["space"]), - thickness: parseDouble(j["thickness"]), - indent: parseDouble(j["leading_indent"]), - endIndent: parseDouble(j["trailing_indent"]), + color: parseColor(value["color"], theme), + space: parseDouble(value["space"]), + thickness: parseDouble(value["thickness"]), + indent: parseDouble(value["leading_indent"]), + endIndent: parseDouble(value["trailing_indent"]), ); } SnackBarThemeData? parseSnackBarTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, + [SnackBarThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.snackBarTheme.copyWith( - backgroundColor: parseColor(theme, j["bgcolor"]), - actionTextColor: parseColor(theme, j["action_text_color"]), - actionBackgroundColor: parseColor(theme, j["action_bgcolor"]), - closeIconColor: parseColor(theme, j["close_icon_color"]), - disabledActionTextColor: parseColor(theme, j["disabled_action_text_color"]), + backgroundColor: parseColor(value["bgcolor"], theme), + actionTextColor: parseColor(value["action_text_color"], theme), + actionBackgroundColor: parseColor(value["action_bgcolor"], theme), + closeIconColor: parseColor(value["close_icon_color"], theme), + disabledActionTextColor: + parseColor(value["disabled_action_text_color"], theme), disabledActionBackgroundColor: - parseColor(theme, j["disabled_action_bgcolor"]), - elevation: parseDouble(j["elevation"]), - shape: j["shape"] != null ? outlinedBorderFromJSON(j["shape"]) : null, - behavior: parseSnackBarBehavior(j["behavior"]), - contentTextStyle: parseTextStyle("content_text_style"), - width: parseDouble(j["width"]), - insetPadding: edgeInsetsFromJson(j["inset_padding"]), - dismissDirection: parseDismissDirection(j["dismiss_direction"]), - showCloseIcon: parseBool(j["show_close_icon"]), - actionOverflowThreshold: parseDouble(j["action_overflow_threshold"]), + parseColor(value["disabled_action_bgcolor"], theme), + elevation: parseDouble(value["elevation"]), + shape: parseShape(value["shape"], theme), + behavior: parseSnackBarBehavior(value["behavior"]), + contentTextStyle: parseTextStyle(value["content_text_style"], theme), + width: parseDouble(value["width"]), + insetPadding: parsePadding(value["inset_padding"]), + dismissDirection: parseDismissDirection(value["dismiss_direction"]), + showCloseIcon: parseBool(value["show_close_icon"]), + actionOverflowThreshold: parseDouble(value["action_overflow_threshold"]), ); } MaterialBannerThemeData? parseBannerTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, + [MaterialBannerThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.bannerTheme.copyWith( - backgroundColor: parseColor(theme, j["bgcolor"]), - elevation: parseDouble(j["elevation"]), - dividerColor: parseColor(theme, j["divider_color"]), - padding: edgeInsetsFromJson(j["padding"]), - leadingPadding: edgeInsetsFromJson(j["leading_padding"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - shadowColor: parseColor(theme, j["shadow_color"]), - contentTextStyle: parseTextStyle("content_text_style"), + backgroundColor: parseColor(value["bgcolor"], theme), + elevation: parseDouble(value["elevation"]), + dividerColor: parseColor(value["divider_color"], theme), + padding: parsePadding(value["padding"]), + leadingPadding: parsePadding(value["leading_padding"]), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + contentTextStyle: parseTextStyle(value["content_text_style"], theme), ); } DatePickerThemeData? parseDatePickerTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, + [DatePickerThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.datePickerTheme.copyWith( - backgroundColor: parseColor(theme, j["bgcolor"]), - elevation: parseDouble(j["elevation"]), - dividerColor: parseColor(theme, j["divider_color"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - shadowColor: parseColor(theme, j["shadow_color"]), - cancelButtonStyle: buttonStyleFromJSON(theme, j["cancel_button_style"]), - confirmButtonStyle: buttonStyleFromJSON(theme, j["confirm_button_style"]), - dayBackgroundColor: getWidgetStateProperty( - j["day_bgcolor"], (jv) => parseColor(theme, jv as String)), - yearStyle: parseTextStyle("year_text_style"), - dayStyle: parseTextStyle("day_text_style"), - shape: j["shape"] != null ? outlinedBorderFromJSON(j["shape"]) : null, - dayOverlayColor: getWidgetStateProperty( - j["day_overlay_color"], (jv) => parseColor(theme, jv as String)), - headerBackgroundColor: parseColor(theme, j["header_bgcolor"]), - dayForegroundColor: getWidgetStateProperty( - j["day_foreground_color"], (jv) => parseColor(theme, jv as String)), - rangePickerElevation: parseDouble(j["range_picker_elevation"]), - todayBackgroundColor: getWidgetStateProperty( - j["today_bgcolor"], (jv) => parseColor(theme, jv as String)), - headerForegroundColor: parseColor(theme, j["header_foreground_color"]), - headerHeadlineStyle: parseTextStyle("header_headline_text_style"), - headerHelpStyle: parseTextStyle("header_help_text_style"), - rangePickerBackgroundColor: parseColor(theme, j["range_picker_bgcolor"]), + backgroundColor: parseColor(value["bgcolor"], theme), + elevation: parseDouble(value["elevation"]), + dividerColor: parseColor(value["divider_color"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + cancelButtonStyle: parseButtonStyle(value["cancel_button_style"], theme), + confirmButtonStyle: parseButtonStyle(value["confirm_button_style"], theme), + dayBackgroundColor: parseWidgetStateColor(value["day_bgcolor"], theme), + yearStyle: parseTextStyle(value["year_text_style"], theme), + dayStyle: parseTextStyle(value["day_text_style"], theme), + shape: parseShape(value["shape"], theme), + dayOverlayColor: parseWidgetStateColor(value["day_overlay_color"], theme), + headerBackgroundColor: parseColor(value["header_bgcolor"], theme), + dayForegroundColor: + parseWidgetStateColor(value["day_foreground_color"], theme), + rangePickerElevation: parseDouble(value["range_picker_elevation"]), + todayBackgroundColor: parseWidgetStateColor(value["today_bgcolor"], theme), + headerForegroundColor: parseColor(value["header_foreground_color"], theme), + headerHeadlineStyle: + parseTextStyle(value["header_headline_text_style"], theme), + headerHelpStyle: parseTextStyle(value["header_help_text_style"], theme), + rangePickerBackgroundColor: + parseColor(value["range_picker_bgcolor"], theme), rangePickerHeaderBackgroundColor: - parseColor(theme, j["range_picker_header_bgcolor"]), + parseColor(value["range_picker_header_bgcolor"], theme), rangePickerHeaderForegroundColor: - parseColor(theme, j["range_picker_header_foreground_color"]), - rangePickerShadowColor: parseColor(theme, j["range_picker_shadow_color"]), - todayForegroundColor: getWidgetStateProperty( - j["today_foreground_color"], (jv) => parseColor(theme, jv as String)), - rangePickerShape: j["range_picker_shape"] != null - ? outlinedBorderFromJSON(j["range_picker_shape"]) - : null, + parseColor(value["range_picker_header_foreground_color"], theme), + rangePickerShadowColor: + parseColor(value["range_picker_shadow_color"], theme), + todayForegroundColor: + parseWidgetStateColor(value["today_foreground_color"], theme), + rangePickerShape: parseShape(value["range_picker_shape"], theme), rangePickerHeaderHelpStyle: - parseTextStyle("range_picker_header_help_text_style"), + parseTextStyle(value["range_picker_header_help_text_style"], theme), rangePickerHeaderHeadlineStyle: - parseTextStyle("range_picker_header_headline_text_style"), + parseTextStyle(value["range_picker_header_headline_text_style"], theme), rangePickerSurfaceTintColor: - parseColor(theme, j["range_picker_surface_tint_color"]), + parseColor(value["range_picker_surface_tint_color"], theme), rangeSelectionBackgroundColor: - parseColor(theme, j["range_selection_bgcolor"]), - rangeSelectionOverlayColor: getWidgetStateProperty( - j["range_selection_overlay_color"], - (jv) => parseColor(theme, jv as String)), - todayBorder: j["today_border_side"] != null - ? borderSideFromJSON(theme, j["today_border_side"]) - : null, - yearBackgroundColor: getWidgetStateProperty( - j["year_bgcolor"], (jv) => parseColor(theme, jv as String)), - yearForegroundColor: getWidgetStateProperty( - j["year_foreground_color"], (jv) => parseColor(theme, jv as String)), - yearOverlayColor: getWidgetStateProperty( - j["year_overlay_color"], (jv) => parseColor(theme, jv as String)), - weekdayStyle: parseTextStyle("weekday_text_style"), - dayShape: getWidgetStateProperty( - j["day_shape"], (jv) => outlinedBorderFromJSON(jv)), - locale: localeFromJSON(j["locale"]), + parseColor(value["range_selection_bgcolor"], theme), + rangeSelectionOverlayColor: + parseWidgetStateColor(value["range_selection_overlay_color"], theme), + todayBorder: parseBorderSide(value["today_border_side"], theme), + yearBackgroundColor: parseWidgetStateColor(value["year_bgcolor"], theme), + yearForegroundColor: + parseWidgetStateColor(value["year_foreground_color"], theme), + yearOverlayColor: parseWidgetStateColor(value["year_overlay_color"], theme), + weekdayStyle: parseTextStyle(value["weekday_text_style"], theme), + dayShape: parseWidgetStateOutlinedBorder(value["day_shape"], theme), + locale: parseLocale(value["locale"]), ); } TimePickerThemeData? parseTimePickerTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, + [TimePickerThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.timePickerTheme.copyWith( - backgroundColor: parseColor(theme, j["bgcolor"]), - elevation: parseDouble(j["elevation"]), - padding: edgeInsetsFromJson(j["padding"]), - shape: j["shape"] != null ? outlinedBorderFromJSON(j["shape"]) : null, - dayPeriodBorderSide: j["day_period_border_side"] != null - ? borderSideFromJSON(theme, j["day_period_border_side"]) - : null, + backgroundColor: parseColor(value["bgcolor"], theme), + elevation: parseDouble(value["elevation"]), + padding: parsePadding(value["padding"]), + shape: parseShape(value["shape"], theme), + dayPeriodBorderSide: + parseBorderSide(value["day_period_border_side"], theme), dayPeriodButtonStyle: - buttonStyleFromJSON(theme, j["day_period_button_style"]), - dayPeriodColor: parseColor(theme, j["day_period_color"]), - dayPeriodShape: j["day_period_shape"] != null - ? outlinedBorderFromJSON(j["day_period_shape"]) - : null, - dayPeriodTextColor: parseColor(theme, j["day_period_text_color"]), - dayPeriodTextStyle: parseTextStyle("day_period_text_style"), - dialBackgroundColor: parseColor(theme, j["dial_bgcolor"]), - dialHandColor: parseColor(theme, j["dial_hand_color"]), - dialTextColor: parseColor(theme, j["dial_text_color"]), - dialTextStyle: parseTextStyle("dial_text_style"), - entryModeIconColor: parseColor(theme, j["entry_mode_icon_color"]), - helpTextStyle: parseTextStyle("help_text_style"), - hourMinuteColor: parseColor(theme, j["hour_minute_color"]), - hourMinuteTextColor: parseColor(theme, j["hour_minute_text_color"]), - hourMinuteTextStyle: parseTextStyle("hour_minute_text_style"), - hourMinuteShape: j["hour_minute_shape"] != null - ? outlinedBorderFromJSON(j["hour_minute_shape"]) - : null, - cancelButtonStyle: buttonStyleFromJSON(theme, j["cancel_button_style"]), - confirmButtonStyle: buttonStyleFromJSON(theme, j["confirm_button_style"]), - timeSelectorSeparatorColor: getWidgetStateProperty( - j["time_selector_separator_color"], - (jv) => parseColor(theme, jv as String)), - timeSelectorSeparatorTextStyle: getWidgetStateProperty( - j["time_selector_separator_text_style"], - (jv) => textStyleFromJson(theme, jv)), + parseButtonStyle(value["day_period_button_style"], theme), + dayPeriodColor: parseColor(value["day_period_color"], theme), + dayPeriodShape: parseShape(value["day_period_shape"], theme), + dayPeriodTextColor: parseColor(value["day_period_text_color"], theme), + dayPeriodTextStyle: parseTextStyle(value["day_period_text_style"], theme), + dialBackgroundColor: parseColor(value["dial_bgcolor"], theme), + dialHandColor: parseColor(value["dial_hand_color"], theme), + dialTextColor: parseColor(value["dial_text_color"], theme), + dialTextStyle: parseTextStyle(value["dial_text_style"], theme), + entryModeIconColor: parseColor(value["entry_mode_icon_color"], theme), + helpTextStyle: parseTextStyle(value["help_text_style"], theme), + hourMinuteColor: parseColor(value["hour_minute_color"], theme), + hourMinuteTextColor: parseColor(value["hour_minute_text_color"], theme), + hourMinuteTextStyle: parseTextStyle(value["hour_minute_text_style"], theme), + hourMinuteShape: parseShape(value["hour_minute_shape"], theme), + cancelButtonStyle: parseButtonStyle(value["cancel_button_style"], theme), + confirmButtonStyle: parseButtonStyle(value["confirm_button_style"], theme), + timeSelectorSeparatorColor: + parseWidgetStateColor(value["time_selector_separator_color"], theme), + timeSelectorSeparatorTextStyle: parseWidgetStateTextStyle( + value["time_selector_separator_text_style"], theme), ); } DropdownMenuThemeData? parseDropdownMenuTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, + [DropdownMenuThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.dropdownMenuTheme.copyWith( - menuStyle: menuStyleFromJSON(theme, j["menu_style"]), - textStyle: parseTextStyle("text_style"), + menuStyle: parseMenuStyle(value["menu_style"], theme), + textStyle: parseTextStyle(value["text_style"], theme), ); } ListTileThemeData? parseListTileTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, + [ListTileThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.listTileTheme.copyWith( - iconColor: parseColor(theme, j["icon_color"]), - textColor: parseColor(theme, j["text_color"]), - tileColor: parseColor(theme, j["bgcolor"]), - shape: j["shape"] != null ? outlinedBorderFromJSON(j["shape"]) : null, - contentPadding: edgeInsetsFromJson(j["content_padding"]), - selectedColor: parseColor(theme, j["selected_color"]), - selectedTileColor: parseColor(theme, j["selected_tile_color"]), - isThreeLine: parseBool(j["is_three_line"]), - visualDensity: parseVisualDensity(j["visual_density"]), - titleTextStyle: parseTextStyle("title_text_style"), - subtitleTextStyle: parseTextStyle("subtitle_text_style"), - minVerticalPadding: parseDouble(j["min_vertical_padding"]), - enableFeedback: parseBool(j["enable_feedback"]), - dense: parseBool(j["dense"]), - horizontalTitleGap: parseDouble(j["horizontal_spacing"]), - minLeadingWidth: parseDouble(j["min_leading_width"]), + iconColor: parseColor(value["icon_color"], theme), + textColor: parseColor(value["text_color"], theme), + tileColor: parseColor(value["bgcolor"], theme), + shape: parseShape(value["shape"], theme), + contentPadding: parsePadding(value["content_padding"]), + selectedColor: parseColor(value["selected_color"], theme), + selectedTileColor: parseColor(value["selected_tile_color"], theme), + isThreeLine: parseBool(value["is_three_line"]), + visualDensity: parseVisualDensity(value["visual_density"]), + titleTextStyle: parseTextStyle(value["title_text_style"], theme), + subtitleTextStyle: parseTextStyle(value["subtitle_text_style"], theme), + minVerticalPadding: parseDouble(value["min_vertical_padding"]), + enableFeedback: parseBool(value["enable_feedback"]), + dense: parseBool(value["dense"]), + horizontalTitleGap: parseDouble(value["horizontal_spacing"]), + minLeadingWidth: parseDouble(value["min_leading_width"]), leadingAndTrailingTextStyle: - parseTextStyle("leading_and_trailing_text_style"), - mouseCursor: getWidgetStateProperty( - j["mouse_cursor"], (jv) => parseMouseCursor(jv)), - minTileHeight: parseDouble(j["min_tile_height"]), + parseTextStyle(value["leading_and_trailing_text_style"], theme), + mouseCursor: parseWidgetStateMouseCursor(value["mouse_cursor"]), + minTileHeight: parseDouble(value["min_tile_height"]), ); } -TooltipThemeData? parseTooltipTheme(ThemeData theme, Map? j) { - if (j == null) { - return null; - } +TooltipThemeData? parseTooltipTheme( + Map? value, BuildContext context, + [TooltipThemeData? defaultValue]) { + if (value == null) return defaultValue; - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + var theme = Theme.of(context); return theme.tooltipTheme.copyWith( - enableFeedback: parseBool(j["enable_feedback"]), - height: parseDouble(j["height"]), - excludeFromSemantics: parseBool(j["exclude_from_semantics"]), - textStyle: parseTextStyle("text_style"), - preferBelow: parseBool(j["prefer_below"]), - verticalOffset: parseDouble(j["vertical_offset"]), - padding: edgeInsetsFromJson(j["padding"]), - waitDuration: durationFromJSON(j["wait_duration"]), - exitDuration: durationFromJSON(j["exit_duration"]), - showDuration: durationFromJSON(j["show_duration"]), - margin: edgeInsetsFromJson(j["margin"]), - textAlign: parseTextAlign(j["text_align"]), - triggerMode: parseTooltipTriggerMode(j["trigger_mode"]), - // TODO: replace null with PageArgsModel - decoration: boxDecorationFromJSON(theme, j["decoration"], null), + enableFeedback: parseBool(value["enable_feedback"]), + height: parseDouble(value["height"]), + excludeFromSemantics: parseBool(value["exclude_from_semantics"]), + textStyle: parseTextStyle(value["text_style"], theme), + preferBelow: parseBool(value["prefer_below"]), + verticalOffset: parseDouble(value["vertical_offset"]), + padding: parsePadding(value["padding"]), + waitDuration: parseDuration(value["wait_duration"]), + exitDuration: parseDuration(value["exit_duration"]), + showDuration: parseDuration(value["show_duration"]), + margin: parseMargin(value["margin"]), + textAlign: parseTextAlign(value["text_align"]), + triggerMode: parseTooltipTriggerMode(value["trigger_mode"]), + decoration: parseBoxDecoration(value["decoration"], context), ); } ExpansionTileThemeData? parseExpansionTileTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } + Map? value, ThemeData theme, + [ExpansionTileThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.expansionTileTheme.copyWith( - backgroundColor: parseColor(theme, j["bgcolor"]), - iconColor: parseColor(theme, j["icon_color"]), - textColor: parseColor(theme, j["text_color"]), - collapsedBackgroundColor: parseColor(theme, j["collapsed_bgcolor"]), - collapsedIconColor: parseColor(theme, j["collapsed_icon_color"]), - clipBehavior: parseClip(j["clip_behavior"]), - collapsedTextColor: parseColor(theme, j["collapsed_text_color"]), - tilePadding: edgeInsetsFromJson(j["tile_padding"]), - expandedAlignment: alignmentFromJson(j["expanded_alignment"]), - childrenPadding: edgeInsetsFromJson(j["controls_padding"]), + backgroundColor: parseColor(value["bgcolor"], theme), + iconColor: parseColor(value["icon_color"], theme), + textColor: parseColor(value["text_color"], theme), + collapsedBackgroundColor: parseColor(value["collapsed_bgcolor"], theme), + collapsedIconColor: parseColor(value["collapsed_icon_color"], theme), + clipBehavior: parseClip(value["clip_behavior"]), + collapsedTextColor: parseColor(value["collapsed_text_color"], theme), + tilePadding: parsePadding(value["tile_padding"]), + expandedAlignment: parseAlignment(value["expanded_alignment"]), + childrenPadding: parsePadding(value["controls_padding"]), ); } -SliderThemeData? parseSliderTheme(ThemeData theme, Map? j) { - if (j == null) { - return null; - } - - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } +SliderThemeData? parseSliderTheme(Map? value, ThemeData theme, + [SliderThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.sliderTheme.copyWith( - activeTrackColor: parseColor(theme, j["active_track_color"]), - inactiveTrackColor: parseColor(theme, j["inactive_track_color"]), - thumbColor: parseColor(theme, j["thumb_color"]), - overlayColor: parseColor(theme, j["overlay_color"]), - valueIndicatorColor: parseColor(theme, j["value_indicator_color"]), - disabledThumbColor: parseColor(theme, j["disabled_thumb_color"]), - valueIndicatorTextStyle: parseTextStyle("value_indicator_text_style"), - mouseCursor: getWidgetStateProperty( - j["mouse_cursor"], (jv) => parseMouseCursor(jv)), - activeTickMarkColor: parseColor(theme, j["active_tick_mark_color"]), + activeTrackColor: parseColor(value["active_track_color"], theme), + inactiveTrackColor: parseColor(value["inactive_track_color"], theme), + thumbColor: parseColor(value["thumb_color"], theme), + overlayColor: parseColor(value["overlay_color"], theme), + valueIndicatorColor: parseColor(value["value_indicator_color"], theme), + disabledThumbColor: parseColor(value["disabled_thumb_color"], theme), + valueIndicatorTextStyle: + parseTextStyle(value["value_indicator_text_style"], theme), + mouseCursor: parseWidgetStateMouseCursor(value["mouse_cursor"]), + activeTickMarkColor: parseColor(value["active_tick_mark_color"], theme), disabledActiveTickMarkColor: - parseColor(theme, j["disabled_active_tick_mark_color"]), + parseColor(value["disabled_active_tick_mark_color"], theme), disabledActiveTrackColor: - parseColor(theme, j["disabled_active_track_color"]), + parseColor(value["disabled_active_track_color"], theme), disabledInactiveTickMarkColor: - parseColor(theme, j["disabled_inactive_tick_mark_color"]), + parseColor(value["disabled_inactive_tick_mark_color"], theme), disabledInactiveTrackColor: - parseColor(theme, j["disabled_inactive_track_color"]), + parseColor(value["disabled_inactive_track_color"], theme), disabledSecondaryActiveTrackColor: - parseColor(theme, j["disabled_secondary_active_track_color"]), - inactiveTickMarkColor: parseColor(theme, j["inactive_tick_mark_color"]), + parseColor(value["disabled_secondary_active_track_color"], theme), + inactiveTickMarkColor: parseColor(value["inactive_tick_mark_color"], theme), overlappingShapeStrokeColor: - parseColor(theme, j["overlapping_shape_stroke_color"]), - minThumbSeparation: parseDouble(j["min_thumb_separation"]), + parseColor(value["overlapping_shape_stroke_color"], theme), + minThumbSeparation: parseDouble(value["min_thumb_separation"]), secondaryActiveTrackColor: - parseColor(theme, j["secondary_active_track_color"]), - trackHeight: parseDouble(j["track_height"]), + parseColor(value["secondary_active_track_color"], theme), + trackHeight: parseDouble(value["track_height"]), valueIndicatorStrokeColor: - parseColor(theme, j["value_indicator_stroke_color"]), - allowedInteraction: parseSliderInteraction(j["interaction"]), - padding: edgeInsetsFromJson(j["padding"]), - trackGap: parseDouble(j["track_gap"]), + parseColor(value["value_indicator_stroke_color"], theme), + allowedInteraction: parseSliderInteraction(value["interaction"]), + padding: parsePadding(value["padding"]), + trackGap: parseDouble(value["track_gap"]), thumbSize: getWidgetStateProperty( - j["thumb_size"], (jv) => sizeFromJson(jv)), + value["thumb_size"], (jv) => parseSize(jv)), // TODO: deprecated in v0.27.0, to be removed in future versions - year2023: parseBool(j["year_2023"]), + year2023: parseBool(value["year_2023"]), ); } ProgressIndicatorThemeData? parseProgressIndicatorTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } + Map? value, ThemeData theme, + [ProgressIndicatorThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.progressIndicatorTheme.copyWith( - color: parseColor(theme, j["color"]), - circularTrackColor: parseColor(theme, j["circular_track_color"]), - linearTrackColor: parseColor(theme, j["linear_track_color"]), - refreshBackgroundColor: parseColor(theme, j["refresh_bgcolor"]), - linearMinHeight: parseDouble(j["linear_min_height"]), - borderRadius: borderRadiusFromJSON(j["border_radius"]), - trackGap: parseDouble(j["track_gap"]), - circularTrackPadding: edgeInsetsFromJson(j["circular_track_padding"]), - constraints: boxConstraintsFromJSON(j["size_constraints"]), - stopIndicatorColor: parseColor(theme, j["stop_indicator_color"]), - stopIndicatorRadius: parseDouble(j["stop_indicator_radius"]), - strokeAlign: parseDouble(j["stroke_align"]), - strokeCap: parseStrokeCap(j["stroke_cap"]), - strokeWidth: parseDouble(j["stroke_width"]), - year2023: parseBool(j["year_2023"]), + color: parseColor(value["color"], theme), + circularTrackColor: parseColor(value["circular_track_color"], theme), + linearTrackColor: parseColor(value["linear_track_color"], theme), + refreshBackgroundColor: parseColor(value["refresh_bgcolor"], theme), + linearMinHeight: parseDouble(value["linear_min_height"]), + borderRadius: parseBorderRadius(value["border_radius"]), + trackGap: parseDouble(value["track_gap"]), + circularTrackPadding: parsePadding(value["circular_track_padding"]), + constraints: parseBoxConstraints(value["size_constraints"]), + stopIndicatorColor: parseColor(value["stop_indicator_color"], theme), + stopIndicatorRadius: parseDouble(value["stop_indicator_radius"]), + strokeAlign: parseDouble(value["stroke_align"]), + strokeCap: parseStrokeCap(value["stroke_cap"]), + strokeWidth: parseDouble(value["stroke_width"]), + year2023: parseBool(value["year_2023"]), ); } PopupMenuThemeData? parsePopupMenuTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, + [PopupMenuThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.popupMenuTheme.copyWith( - color: parseColor(theme, j["color"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - shadowColor: parseColor(theme, j["shadow_color"]), - iconColor: parseColor(theme, j["icon_color"]), - textStyle: parseTextStyle("text_style"), - labelTextStyle: getWidgetStateProperty( - j["label_text_style"], (jv) => textStyleFromJson(theme, jv)), - enableFeedback: parseBool(j["enable_feedback"]), - elevation: parseDouble(j["elevation"]), - iconSize: parseDouble(j["icon_size"]), - position: j["menu_position"] != null - ? PopupMenuPosition.values.firstWhereOrNull( - (c) => c.name.toLowerCase() == j["menu_position"].toLowerCase()) - : null, - mouseCursor: getWidgetStateProperty( - j["mouse_cursor"], (jv) => parseMouseCursor(jv)), - shape: j["shape"] != null ? outlinedBorderFromJSON(j["shape"]) : null, - menuPadding: edgeInsetsFromJson(j["menu_padding"]), + color: parseColor(value["color"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + iconColor: parseColor(value["icon_color"], theme), + textStyle: parseTextStyle(value["text_style"], theme), + labelTextStyle: parseWidgetStateTextStyle(value["label_text_style"], theme), + enableFeedback: parseBool(value["enable_feedback"]), + elevation: parseDouble(value["elevation"]), + iconSize: parseDouble(value["icon_size"]), + position: parsePopupMenuPosition(value["menu_position"]), + mouseCursor: parseWidgetStateMouseCursor(value["mouse_cursor"]), + shape: parseShape(value["shape"], theme), + menuPadding: parsePadding(value["menu_padding"]), ); } SearchBarThemeData? parseSearchBarTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } + Map? value, ThemeData theme, + [SearchBarThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.searchBarTheme.copyWith( - surfaceTintColor: getWidgetStateProperty( - j["surface_tint_color"], (jv) => parseColor(theme, jv as String)), - shadowColor: getWidgetStateProperty( - j["shadow_color"], (jv) => parseColor(theme, jv as String)), - elevation: getWidgetStateProperty( - j["elevation"], (jv) => parseDouble(jv)), - backgroundColor: getWidgetStateProperty( - j["bgcolor"], (jv) => parseColor(theme, jv as String)), - overlayColor: getWidgetStateProperty( - j["overlay_color"], (jv) => parseColor(theme, jv as String)), - textStyle: getWidgetStateProperty( - j["text_style"], (jv) => textStyleFromJson(theme, jv)), - hintStyle: getWidgetStateProperty( - j["hint_style"], (jv) => textStyleFromJson(theme, jv)), - shape: getWidgetStateProperty( - j["shape"], (jv) => outlinedBorderFromJSON(jv)), - textCapitalization: j["text_capitalization"] != null - ? TextCapitalization.values.firstWhereOrNull((c) => - c.name.toLowerCase() == j["text_capitalization"].toLowerCase()) - : null, - padding: getWidgetStateProperty( - j["padding"], (jv) => edgeInsetsFromJson(jv)), - constraints: boxConstraintsFromJSON(j["size_constraints"]), - side: getWidgetStateProperty( - j["border_side"], (jv) => borderSideFromJSON(theme, jv)), + surfaceTintColor: parseWidgetStateColor(value["surface_tint_color"], theme), + shadowColor: parseWidgetStateColor(value["shadow_color"], theme), + elevation: parseWidgetStateDouble(value["elevation"]), + backgroundColor: parseWidgetStateColor(value["bgcolor"], theme), + overlayColor: parseWidgetStateColor(value["overlay_color"], theme), + textStyle: parseWidgetStateTextStyle(value["text_style"], theme), + hintStyle: parseWidgetStateTextStyle(value["hint_style"], theme), + shape: parseWidgetStateOutlinedBorder(value["shape"], theme), + textCapitalization: parseTextCapitalization(value["text_capitalization"]), + padding: parseWidgetStatePadding(value["padding"]), + constraints: parseBoxConstraints(value["size_constraints"]), + side: parseWidgetStateBorderSide(value["border_side"], theme), ); } SearchViewThemeData? parseSearchViewTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - - TextStyle? parseTextStyle(String propName) { - return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null; - } + Map? value, ThemeData theme, + [SearchViewThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.searchViewTheme.copyWith( - backgroundColor: parseColor(theme, j["bgcolor"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - dividerColor: parseColor(theme, j["divider_color"]), - elevation: parseDouble(j["elevation"]), - headerHintStyle: parseTextStyle("header_hint_text_style"), - headerTextStyle: parseTextStyle("header_text_style"), - shape: j["shape"] != null ? outlinedBorderFromJSON(j["shape"]) : null, - side: borderSideFromJSON(theme, j["border_side"]), - constraints: boxConstraintsFromJSON(j["size_constraints"]), - headerHeight: parseDouble(j["header_height"]), - padding: edgeInsetsFromJson(j["padding"]), - barPadding: edgeInsetsFromJson(j["bar_padding"]), - shrinkWrap: parseBool(j["shrink_wrap"]), + backgroundColor: parseColor(value["bgcolor"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + dividerColor: parseColor(value["divider_color"], theme), + elevation: parseDouble(value["elevation"]), + headerHintStyle: parseTextStyle(value["header_hint_text_style"], theme), + headerTextStyle: parseTextStyle(value["header_text_style"], theme), + shape: parseShape(value["shape"], theme), + side: parseBorderSide(value["border_side"], theme), + constraints: parseBoxConstraints(value["size_constraints"]), + headerHeight: parseDouble(value["header_height"]), + padding: parsePadding(value["padding"]), + barPadding: parsePadding(value["bar_padding"]), + shrinkWrap: parseBool(value["shrink_wrap"]), ); } NavigationDrawerThemeData? parseNavigationDrawerTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } + Map? value, ThemeData theme, + [NavigationDrawerThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.navigationDrawerTheme.copyWith( - backgroundColor: parseColor(theme, j["bgcolor"]), - shadowColor: parseColor(theme, j["shadow_color"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - indicatorColor: parseColor(theme, j["indicator_color"]), - elevation: parseDouble(j["elevation"]), - indicatorSize: sizeFromJson(j["indicator_size"]), - tileHeight: parseDouble(j["tile_height"]), - labelTextStyle: getWidgetStateProperty( - j["label_text_style"], (jv) => textStyleFromJson(theme, jv)), - indicatorShape: j["indicator_shape"] != null - ? outlinedBorderFromJSON(j["indicator_shape"]) - : null, + backgroundColor: parseColor(value["bgcolor"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + indicatorColor: parseColor(value["indicator_color"], theme), + elevation: parseDouble(value["elevation"]), + indicatorSize: parseSize(value["indicator_size"]), + tileHeight: parseDouble(value["tile_height"]), + labelTextStyle: parseWidgetStateTextStyle(value["label_text_style"], theme), + indicatorShape: parseShape(value["indicator_shape"], theme), ); } NavigationBarThemeData? parseNavigationBarTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } + Map? value, ThemeData theme, + [NavigationBarThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.navigationBarTheme.copyWith( - backgroundColor: parseColor(theme, j["bgcolor"]), - shadowColor: parseColor(theme, j["shadow_color"]), - surfaceTintColor: parseColor(theme, j["surface_tint_color"]), - indicatorColor: parseColor(theme, j["indicator_color"]), - overlayColor: getWidgetStateProperty( - j["overlay_color"], (jv) => parseColor(theme, jv as String)), - elevation: parseDouble(j["elevation"]), - height: parseDouble(j["height"]), - labelTextStyle: getWidgetStateProperty( - j["label_text_style"], (jv) => textStyleFromJson(theme, jv)), - indicatorShape: j["indicator_shape"] != null - ? outlinedBorderFromJSON(j["indicator_shape"]) - : null, - labelBehavior: j["label_behavior"] != null - ? NavigationDestinationLabelBehavior.values.firstWhereOrNull( - (c) => c.name.toLowerCase() == j["label_behavior"].toLowerCase()) - : null, - labelPadding: edgeInsetsFromJson(j["label_padding"]), + backgroundColor: parseColor(value["bgcolor"], theme), + shadowColor: parseColor(value["shadow_color"], theme), + surfaceTintColor: parseColor(value["surface_tint_color"], theme), + indicatorColor: parseColor(value["indicator_color"], theme), + overlayColor: parseWidgetStateColor(value["overlay_color"], theme), + elevation: parseDouble(value["elevation"]), + height: parseDouble(value["height"]), + labelTextStyle: parseWidgetStateTextStyle(value["label_text_style"], theme), + indicatorShape: parseShape(value["indicator_shape"], theme), + labelBehavior: + parseNavigationDestinationLabelBehavior(value["label_behavior"]), + labelPadding: parsePadding(value["label_padding"]), ); } SegmentedButtonThemeData? parseSegmentedButtonTheme( - ThemeData theme, Map? j) { - if (j == null) { - return null; - } - var selected_icon = parseIcon(j["selected_icon"]); + Map? value, ThemeData theme, + [SegmentedButtonThemeData? defaultValue]) { + if (value == null) return defaultValue; + var selectedIcon = parseIcon(value["selected_icon"]); return theme.segmentedButtonTheme.copyWith( - selectedIcon: selected_icon != null ? Icon(selected_icon) : null, - style: buttonStyleFromJSON(theme, j["style"]), + selectedIcon: selectedIcon != null ? Icon(selectedIcon) : null, + style: parseButtonStyle(value["style"], theme), ); } -IconThemeData? parseIconTheme(ThemeData theme, Map? j) { - if (j == null) { - return null; - } +IconThemeData? parseIconTheme(Map? value, ThemeData theme, + [IconThemeData? defaultValue]) { + if (value == null) return defaultValue; return theme.iconTheme.copyWith( - color: parseColor(theme, j["color"]), - applyTextScaling: parseBool(j["apply_text_scaling"]), - fill: parseDouble(j["fill"]), - opacity: parseDouble(j["opacity"]), - size: parseDouble(j["size"]), - opticalSize: parseDouble(j["optical_size"]), - grade: parseDouble(j["grade"]), - weight: parseDouble(j["weight"]), - shadows: - j["shadows"] != null ? boxShadowsFromJSON(theme, j["shadows"]) : null, + color: parseColor(value["color"], theme), + applyTextScaling: parseBool(value["apply_text_scaling"]), + fill: parseDouble(value["fill"]), + opacity: parseDouble(value["opacity"]), + size: parseDouble(value["size"]), + opticalSize: parseDouble(value["optical_size"]), + grade: parseDouble(value["grade"]), + weight: parseDouble(value["weight"]), + shadows: parseBoxShadows(value["shadows"], theme), ); } -PageTransitionsBuilder parseTransitionsBuilder( - String? tb, PageTransitionsBuilder defaultBuilder) { - switch (tb?.toLowerCase()) { - case "fadeupwards": - return const FadeUpwardsPageTransitionsBuilder(); - case "openupwards": - return const OpenUpwardsPageTransitionsBuilder(); - case "cupertino": - return const CupertinoPageTransitionsBuilder(); - case "zoom": - return const ZoomPageTransitionsBuilder(); - case "none": - return const NoPageTransitionsBuilder(); - case "predictive": - return const PredictiveBackPageTransitionsBuilder(); - case "fadeforwards": - return const FadeForwardsPageTransitionsBuilder(); - default: - return defaultBuilder; - } +PageTransitionsBuilder? parseTransitionsBuilder(String? value, + [PageTransitionsBuilder? defaultValue]) { + var buildersMap = { + "fadeupwards": const FadeUpwardsPageTransitionsBuilder(), + "openupwards": const OpenUpwardsPageTransitionsBuilder(), + "cupertino": const CupertinoPageTransitionsBuilder(), + "zoom": const ZoomPageTransitionsBuilder(), + "none": const NoPageTransitionsBuilder(), + "predictive": const PredictiveBackPageTransitionsBuilder(), + "fadeforwards": const FadeForwardsPageTransitionsBuilder(), + }; + return buildersMap[value?.toLowerCase()] ?? defaultValue; } class NoPageTransitionsBuilder extends PageTransitionsBuilder { @@ -1500,13 +1299,363 @@ class NoPageTransitionsBuilder extends PageTransitionsBuilder { @override Widget buildTransitions( - PageRoute? route, - BuildContext? context, - Animation animation, - Animation secondaryAnimation, - Widget? child, - ) { + PageRoute? route, + BuildContext? context, + Animation animation, + Animation secondaryAnimation, + Widget? child) { // only return the child without warping it with animations return child!; } } + +// Trying to fix https://github.com/flutter/flutter/issues/165455 +// TODO: remove this when the fix is available +bool themesEqual(ThemeData a, ThemeData b) { + return mapEquals(a.adaptationMap, b.adaptationMap) && + a.applyElevationOverlayColor == b.applyElevationOverlayColor && + //a.cupertinoOverrideTheme == b.cupertinoOverrideTheme && + mapEquals(a.extensions, b.extensions) && + a.inputDecorationTheme == b.inputDecorationTheme && + a.materialTapTargetSize == b.materialTapTargetSize && + a.pageTransitionsTheme == b.pageTransitionsTheme && + a.platform == b.platform && + a.scrollbarTheme == b.scrollbarTheme && + a.splashFactory == b.splashFactory && + a.useMaterial3 == b.useMaterial3 && + a.visualDensity == b.visualDensity && + // COLOR + a.canvasColor == b.canvasColor && + a.cardColor == b.cardColor && + a.colorScheme == b.colorScheme && + a.disabledColor == b.disabledColor && + a.dividerColor == b.dividerColor && + a.focusColor == b.focusColor && + a.highlightColor == b.highlightColor && + a.hintColor == b.hintColor && + a.hoverColor == b.hoverColor && + a.indicatorColor == b.indicatorColor && + a.primaryColor == b.primaryColor && + a.primaryColorDark == b.primaryColorDark && + a.primaryColorLight == b.primaryColorLight && + a.scaffoldBackgroundColor == b.scaffoldBackgroundColor && + a.secondaryHeaderColor == b.secondaryHeaderColor && + a.shadowColor == b.shadowColor && + a.splashColor == b.splashColor && + a.unselectedWidgetColor == b.unselectedWidgetColor && + // TYPOGRAPHY & ICONOGRAPHY + a.iconTheme == b.iconTheme && + a.primaryIconTheme == b.primaryIconTheme && + a.primaryTextTheme == b.primaryTextTheme && + a.textTheme == b.textTheme && + a.typography == b.typography && + // COMPONENT THEMES + a.actionIconTheme == b.actionIconTheme && + a.appBarTheme == b.appBarTheme && + a.badgeTheme == b.badgeTheme && + a.bannerTheme == b.bannerTheme && + a.bottomAppBarTheme == b.bottomAppBarTheme && + a.bottomNavigationBarTheme == b.bottomNavigationBarTheme && + a.bottomSheetTheme == b.bottomSheetTheme && + a.buttonTheme == b.buttonTheme && + a.cardTheme == b.cardTheme && + a.checkboxTheme == b.checkboxTheme && + a.chipTheme == b.chipTheme && + a.dataTableTheme == b.dataTableTheme && + a.datePickerTheme == b.datePickerTheme && + a.dialogTheme == b.dialogTheme && + a.dividerTheme == b.dividerTheme && + a.drawerTheme == b.drawerTheme && + a.dropdownMenuTheme == b.dropdownMenuTheme && + a.elevatedButtonTheme == b.elevatedButtonTheme && + a.expansionTileTheme == b.expansionTileTheme && + a.filledButtonTheme == b.filledButtonTheme && + a.floatingActionButtonTheme == b.floatingActionButtonTheme && + a.iconButtonTheme == b.iconButtonTheme && + a.listTileTheme == b.listTileTheme && + a.menuBarTheme == b.menuBarTheme && + a.menuButtonTheme == b.menuButtonTheme && + a.menuTheme == b.menuTheme && + a.navigationBarTheme == b.navigationBarTheme && + a.navigationDrawerTheme == b.navigationDrawerTheme && + a.navigationRailTheme == b.navigationRailTheme && + a.outlinedButtonTheme == b.outlinedButtonTheme && + a.popupMenuTheme == b.popupMenuTheme && + a.progressIndicatorTheme == b.progressIndicatorTheme && + a.radioTheme == b.radioTheme && + a.searchBarTheme == b.searchBarTheme && + a.searchViewTheme == b.searchViewTheme && + a.segmentedButtonTheme == b.segmentedButtonTheme && + a.sliderTheme == b.sliderTheme && + a.snackBarTheme == b.snackBarTheme && + a.switchTheme == b.switchTheme && + a.tabBarTheme == b.tabBarTheme && + a.textButtonTheme == b.textButtonTheme && + a.textSelectionTheme == b.textSelectionTheme && + a.timePickerTheme == b.timePickerTheme && + a.toggleButtonsTheme == b.toggleButtonsTheme && + a.tooltipTheme == b.tooltipTheme; +} + +extension ThemeParsers on Control { + Brightness? getBrightness(String propertyName, [Brightness? defaultValue]) { + return parseBrightness(get(propertyName), defaultValue); + } + + ThemeData getTheme( + String propertyName, BuildContext context, Brightness? brightness, + {ThemeData? parentTheme}) { + return parseTheme(get(propertyName), context, brightness, + parentTheme: parentTheme); + } + + ThemeMode? getThemeMode(String propertyName, [ThemeMode? defaultValue]) { + return parseThemeMode(get(propertyName), defaultValue); + } + + CupertinoThemeData? getCupertinoTheme( + String propertyName, BuildContext context, Brightness? brightness, + {ThemeData? parentTheme}) { + return parseCupertinoTheme(get(propertyName), context, brightness, + parentTheme: parentTheme); + } + + ColorScheme? getColorScheme(String propertyName, ThemeData theme, + [ColorScheme? defaultValue]) { + return parseColorScheme(get(propertyName), theme, defaultValue); + } + + TextTheme? getTextTheme( + String propertyName, ThemeData theme, TextTheme textTheme, + [TextTheme? defaultValue]) { + return parseTextTheme(get(propertyName), theme, textTheme, defaultValue); + } + + VisualDensity? getVisualDensity(String propertyName, + [VisualDensity? defaultValue]) { + return parseVisualDensity(get(propertyName), defaultValue); + } + + PageTransitionsTheme? getPageTransitionsTheme(String propertyName, + [PageTransitionsTheme? defaultValue]) { + return parsePageTransitions(get(propertyName), defaultValue); + } + + SystemUiOverlayStyleTheme getSystemUiOverlayStyleTheme( + String propertyName, ThemeData theme, Brightness? brightness) { + return SystemUiOverlayStyleTheme( + get(propertyName) != null + ? parseSystemUiOverlayStyle(get(propertyName), theme, brightness) + : null, + ); + } + + ButtonThemeData? getButtonTheme(String propertyName, ThemeData theme, + [ButtonThemeData? defaultValue]) { + return parseButtonTheme(get(propertyName), theme, defaultValue); + } + + ElevatedButtonThemeData? getElevatedButtonTheme( + String propertyName, ThemeData theme, + [ElevatedButtonThemeData? defaultValue]) { + return parseElevatedButtonTheme(get(propertyName), theme, defaultValue); + } + + OutlinedButtonThemeData? getOutlinedButtonTheme( + String propertyName, ThemeData theme, + [OutlinedButtonThemeData? defaultValue]) { + return parseOutlinedButtonTheme(get(propertyName), theme, defaultValue); + } + + TextButtonThemeData? getTextButtonTheme(String propertyName, ThemeData theme, + [TextButtonThemeData? defaultValue]) { + return parseTextButtonTheme(get(propertyName), theme, defaultValue); + } + + FilledButtonThemeData? getFilledButtonTheme( + String propertyName, ThemeData theme, + [FilledButtonThemeData? defaultValue]) { + return parseFilledButtonTheme(get(propertyName), theme, defaultValue); + } + + IconButtonThemeData? getIconButtonTheme(String propertyName, ThemeData theme, + [IconButtonThemeData? defaultValue]) { + return parseIconButtonTheme(get(propertyName), theme, defaultValue); + } + + DataTableThemeData? getDataTableTheme( + String propertyName, BuildContext context, + [DataTableThemeData? defaultValue]) { + return parseDataTableTheme(get(propertyName), context, defaultValue); + } + + ScrollbarThemeData? getScrollbarTheme(String propertyName, ThemeData theme, + [ScrollbarThemeData? defaultValue]) { + return parseScrollBarTheme(get(propertyName), theme, defaultValue); + } + + TabBarThemeData? getTabBarTheme(String propertyName, ThemeData theme, + [TabBarThemeData? defaultValue]) { + return parseTabBarTheme(get(propertyName), theme, defaultValue); + } + + DialogThemeData? getDialogTheme(String propertyName, ThemeData theme, + [DialogThemeData? defaultValue]) { + return parseDialogTheme(get(propertyName), theme, defaultValue); + } + + BottomSheetThemeData? getBottomSheetTheme( + String propertyName, ThemeData theme, + [BottomSheetThemeData? defaultValue]) { + return parseBottomSheetTheme(get(propertyName), theme, defaultValue); + } + + CardThemeData? getCardTheme(String propertyName, ThemeData theme, + [CardThemeData? defaultValue]) { + return parseCardTheme(get(propertyName), theme, defaultValue); + } + + ChipThemeData? getChipTheme(String propertyName, ThemeData theme, + [ChipThemeData? defaultValue]) { + return parseChipTheme(get(propertyName), theme, defaultValue); + } + + FloatingActionButtonThemeData? getFloatingActionButtonTheme( + String propertyName, ThemeData theme, + [FloatingActionButtonThemeData? defaultValue]) { + return parseFloatingActionButtonTheme( + get(propertyName), theme, defaultValue); + } + + NavigationRailThemeData? getNavigationRailTheme( + String propertyName, ThemeData theme, + [NavigationRailThemeData? defaultValue]) { + return parseNavigationRailTheme(get(propertyName), theme, defaultValue); + } + + AppBarTheme? getAppBarTheme(String propertyName, ThemeData theme, + [AppBarTheme? defaultValue]) { + return parseAppBarTheme(get(propertyName), theme, defaultValue); + } + + BottomAppBarTheme? getBottomAppBarTheme(String propertyName, ThemeData theme, + [BottomAppBarTheme? defaultValue]) { + return parseBottomAppBarTheme(get(propertyName), theme, defaultValue); + } + + RadioThemeData? getRadioTheme(String propertyName, ThemeData theme, + [RadioThemeData? defaultValue]) { + return parseRadioTheme(get(propertyName), theme, defaultValue); + } + + CheckboxThemeData? getCheckboxTheme(String propertyName, ThemeData theme, + [CheckboxThemeData? defaultValue]) { + return parseCheckboxTheme(get(propertyName), theme, defaultValue); + } + + BadgeThemeData? getBadgeTheme(String propertyName, ThemeData theme, + [BadgeThemeData? defaultValue]) { + return parseBadgeTheme(get(propertyName), theme, defaultValue); + } + + SwitchThemeData? getSwitchTheme(String propertyName, ThemeData theme, + [SwitchThemeData? defaultValue]) { + return parseSwitchTheme(get(propertyName), theme, defaultValue); + } + + DividerThemeData? getDividerTheme(String propertyName, ThemeData theme, + [DividerThemeData? defaultValue]) { + return parseDividerTheme(get(propertyName), theme, defaultValue); + } + + SnackBarThemeData? getSnackBarTheme(String propertyName, ThemeData theme, + [SnackBarThemeData? defaultValue]) { + return parseSnackBarTheme(get(propertyName), theme, defaultValue); + } + + MaterialBannerThemeData? getBannerTheme(String propertyName, ThemeData theme, + [MaterialBannerThemeData? defaultValue]) { + return parseBannerTheme(get(propertyName), theme, defaultValue); + } + + DatePickerThemeData? getDatePickerTheme(String propertyName, ThemeData theme, + [DatePickerThemeData? defaultValue]) { + return parseDatePickerTheme(get(propertyName), theme, defaultValue); + } + + TimePickerThemeData? getTimePickerTheme(String propertyName, ThemeData theme, + [TimePickerThemeData? defaultValue]) { + return parseTimePickerTheme(get(propertyName), theme, defaultValue); + } + + DropdownMenuThemeData? getDropdownMenuTheme( + String propertyName, ThemeData theme, + [DropdownMenuThemeData? defaultValue]) { + return parseDropdownMenuTheme(get(propertyName), theme, defaultValue); + } + + ListTileThemeData? getListTileTheme(String propertyName, ThemeData theme, + [ListTileThemeData? defaultValue]) { + return parseListTileTheme(get(propertyName), theme, defaultValue); + } + + TooltipThemeData? getTooltipTheme(String propertyName, BuildContext context, + [TooltipThemeData? defaultValue]) { + return parseTooltipTheme(get(propertyName), context, defaultValue); + } + + ExpansionTileThemeData? getExpansionTileTheme( + String propertyName, ThemeData theme, + [ExpansionTileThemeData? defaultValue]) { + return parseExpansionTileTheme(get(propertyName), theme, defaultValue); + } + + SliderThemeData? getSliderTheme(String propertyName, ThemeData theme, + [SliderThemeData? defaultValue]) { + return parseSliderTheme(get(propertyName), theme, defaultValue); + } + + ProgressIndicatorThemeData? getProgressIndicatorTheme( + String propertyName, ThemeData theme, + [ProgressIndicatorThemeData? defaultValue]) { + return parseProgressIndicatorTheme(get(propertyName), theme, defaultValue); + } + + PopupMenuThemeData? getPopupMenuTheme(String propertyName, ThemeData theme, + [PopupMenuThemeData? defaultValue]) { + return parsePopupMenuTheme(get(propertyName), theme, defaultValue); + } + + SearchBarThemeData? getSearchBarTheme(String propertyName, ThemeData theme, + [SearchBarThemeData? defaultValue]) { + return parseSearchBarTheme(get(propertyName), theme, defaultValue); + } + + SearchViewThemeData? getSearchViewTheme(String propertyName, ThemeData theme, + [SearchViewThemeData? defaultValue]) { + return parseSearchViewTheme(get(propertyName), theme, defaultValue); + } + + NavigationDrawerThemeData? getNavigationDrawerTheme( + String propertyName, ThemeData theme, + [NavigationDrawerThemeData? defaultValue]) { + return parseNavigationDrawerTheme(get(propertyName), theme, defaultValue); + } + + NavigationBarThemeData? getNavigationBarTheme( + String propertyName, ThemeData theme, + [NavigationBarThemeData? defaultValue]) { + return parseNavigationBarTheme(get(propertyName), theme, defaultValue); + } + + SegmentedButtonThemeData? getSegmentedButtonTheme( + String propertyName, ThemeData theme, + [SegmentedButtonThemeData? defaultValue]) { + return parseSegmentedButtonTheme(get(propertyName), theme, defaultValue); + } + + IconThemeData? getIconTheme(String propertyName, ThemeData theme, + [IconThemeData? defaultValue]) { + return parseIconTheme(get(propertyName), theme, defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/time.dart b/packages/flet/lib/src/utils/time.dart index 57e69d23c..acaf287a4 100644 --- a/packages/flet/lib/src/utils/time.dart +++ b/packages/flet/lib/src/utils/time.dart @@ -1,37 +1,151 @@ -import 'dart:convert'; +import 'package:collection/collection.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import '../models/control.dart'; import 'numbers.dart'; -Duration? parseDuration(Control control, String propName, - [Duration? defaultValue]) { - var v = control.attrString(propName, null); - if (v == null) { - return defaultValue; +enum DurationUnit { microseconds, milliseconds, seconds, minutes, hours, days } + +/// Parses a dynamic [value] into a [Duration] object. +/// +/// Supported input types: +/// - `null`: Returns [defaultValue]. +/// - `num` (int or double): Interpreted as a single time unit specified by [treatNumAs]. +/// - `Map`: Must contain one or more of the following keys with numeric values: +/// `days`, `hours`, `minutes`, `seconds`, `milliseconds`, `microseconds`. +/// +/// Parameters: +/// - [value]: The input to parse. Can be `null`, `num`, or `Map`. +/// - [defaultValue]: The value to return if [value] is `null`. +/// - [treatNumAs]: Specifies the unit of time for numeric input. Defaults to `DurationUnit.milliseconds`. +/// +/// Returns: +/// A [Duration] constructed from the parsed input, or [defaultValue] if input is `null`. +Duration? parseDuration(dynamic value, + [Duration? defaultValue, + DurationUnit treatNumAs = DurationUnit.milliseconds]) { + if (value == null) return defaultValue; + if (value is num) { + final v = parseInt(value, 0)!; + return Duration( + microseconds: treatNumAs == DurationUnit.microseconds ? v : 0, + milliseconds: treatNumAs == DurationUnit.milliseconds ? v : 0, + seconds: treatNumAs == DurationUnit.seconds ? v : 0, + minutes: treatNumAs == DurationUnit.minutes ? v : 0, + hours: treatNumAs == DurationUnit.hours ? v : 0, + days: treatNumAs == DurationUnit.days ? v : 0, + ); } + return Duration( + days: parseInt(value["days"], 0)!, + hours: parseInt(value["hours"], 0)!, + minutes: parseInt(value["minutes"], 0)!, + seconds: parseInt(value["seconds"], 0)!, + milliseconds: parseInt(value["milliseconds"], 0)!, + microseconds: parseInt(value["microseconds"], 0)!); +} + +TimePickerEntryMode? parseTimePickerEntryMode(String? value, + [TimePickerEntryMode? defaultValue]) { + if (value == null) return defaultValue; + return TimePickerEntryMode.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +CupertinoDatePickerMode? parseCupertinoDatePickerMode(String? value, + [CupertinoDatePickerMode? defaultValue]) { + if (value == null) return defaultValue; + return CupertinoDatePickerMode.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} - final j1 = json.decode(v); - return durationFromJSON(j1); +CupertinoTimerPickerMode? parseCupertinoTimerPickerMode(String? value, + [CupertinoTimerPickerMode? defaultValue]) { + if (value == null) return defaultValue; + return CupertinoTimerPickerMode.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; } -Duration? durationFromJSON(dynamic json, [Duration? defaultValue]) { - if (json == null) { - return defaultValue; +DatePickerDateOrder? parseDatePickerDateOrder(String? value, + [DatePickerDateOrder? defaultValue]) { + if (value == null) return defaultValue; + return DatePickerDateOrder.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +DatePickerEntryMode? parseDatePickerEntryMode(String? value, + [DatePickerEntryMode? defaultValue]) { + if (value == null) return defaultValue; + return DatePickerEntryMode.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +DatePickerMode? parseDatePickerMode(String? value, + [DatePickerMode? defaultValue]) { + if (value == null) return defaultValue; + return DatePickerMode.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +extension TimeParsers on Control { + /// Retrieves and parses a duration value from the control's properties. + /// + /// Parameters: + /// - [propertyName]: The name of the property to retrieve the value from. + /// - [defaultValue]: The value to return if the property is not set or is `null`. + /// - [treatNumAs]: Specifies the unit of time for numeric input. Defaults to `DurationUnit.milliseconds`. + /// + /// + /// Returns: + /// A [Duration] based on the property's value, or [defaultValue] if the value is `null`. + Duration? getDuration(String propertyName, + [Duration? defaultValue, + DurationUnit treatNumAs = DurationUnit.milliseconds]) { + return parseDuration(get(propertyName), defaultValue, treatNumAs); } - if (json is int || json is double) { - return Duration(milliseconds: parseInt(json, 0)!); + + DatePickerDateOrder? getDatePickerDateOrder(String propertyName, + [DatePickerDateOrder? defaultValue]) { + return parseDatePickerDateOrder(get(propertyName), defaultValue); } - return Duration( - days: parseInt(json["days"], 0)!, - hours: parseInt(json["hours"], 0)!, - minutes: parseInt(json["minutes"], 0)!, - seconds: parseInt(json["seconds"], 0)!, - milliseconds: parseInt(json["milliseconds"], 0)!, - microseconds: parseInt(json["microseconds"], 0)!); -} - -Duration? durationFromString(String? duration, [Duration? defaultValue]) { - return duration != null - ? durationFromJSON(json.decode(duration), defaultValue) - : defaultValue; -} \ No newline at end of file + + TimePickerEntryMode? getTimePickerEntryMode(String propertyName, + [TimePickerEntryMode? defaultValue]) { + return parseTimePickerEntryMode(get(propertyName), defaultValue); + } + + CupertinoDatePickerMode? getCupertinoDatePickerMode(String propertyName, + [CupertinoDatePickerMode? defaultValue]) { + return parseCupertinoDatePickerMode(get(propertyName), defaultValue); + } + + CupertinoTimerPickerMode? getCupertinoTimerPickerMode(String propertyName, + [CupertinoTimerPickerMode? defaultValue]) { + return parseCupertinoTimerPickerMode(get(propertyName), defaultValue); + } + + DatePickerMode? getDatePickerMode(String propertyName, + [DatePickerMode? defaultValue]) { + return parseDatePickerMode(get(propertyName), defaultValue); + } + + DatePickerEntryMode? getDatePickerEntryMode(String propertyName, + [DatePickerEntryMode? defaultValue]) { + return parseDatePickerEntryMode(get(propertyName), defaultValue); + } + + DateTime? getDateTime(String propertyName, [DateTime? defaultValue]) { + return get(propertyName, defaultValue); + } + + TimeOfDay? getTimeOfDay(String propertyName, [TimeOfDay? defaultValue]) { + return get(propertyName, defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/tooltip.dart b/packages/flet/lib/src/utils/tooltip.dart index a3aafd7da..3ca031e02 100644 --- a/packages/flet/lib/src/utils/tooltip.dart +++ b/packages/flet/lib/src/utils/tooltip.dart @@ -1,84 +1,72 @@ -import 'dart:convert'; - import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import '../models/control.dart'; -import 'borders.dart'; import 'box.dart'; import 'colors.dart'; import 'edge_insets.dart'; -import 'gradient.dart'; -import 'images.dart'; +import 'mouse.dart'; import 'numbers.dart'; -import 'others.dart'; import 'text.dart'; import 'time.dart'; TooltipTriggerMode? parseTooltipTriggerMode(String? value, [TooltipTriggerMode? defaultValue]) { - if (value == null) { - return defaultValue; - } + if (value == null) return defaultValue; return TooltipTriggerMode.values.firstWhereOrNull( (e) => e.name.toLowerCase() == value.toLowerCase()) ?? defaultValue; } -Tooltip? parseTooltip( - Control control, String propName, Widget widget, ThemeData theme) { - var v = control.attrString(propName, null); - if (v == null) { - return null; - } - final j = json.decode(v); - return tooltipFromJSON(j, widget, theme); -} - -Tooltip? tooltipFromJSON(dynamic j, Widget widget, ThemeData theme) { - if (j == null) { +Tooltip? parseTooltip(dynamic value, BuildContext context, Widget widget) { + const defaultWaitDuration = Duration(milliseconds: 800); + if (value == null) { return null; - } else if (j is String) { + } else if (value is String) { return Tooltip( - message: j, - padding: const EdgeInsets.all(4.0), - waitDuration: const Duration(milliseconds: 800), + message: value, + waitDuration: defaultWaitDuration, child: widget, ); } + var theme = Theme.of(context); + /// The tooltip shape defaults to a rounded rectangle with a border radius of /// 4.0. Tooltips will also default to an opacity of 90% - var decoration = boxDecorationFromDetails( - gradient: gradientFromJSON(theme, j["gradient"]), - border: borderFromJSON(theme, j["border"]), - borderRadius: - borderRadiusFromJSON(j["border_radius"], BorderRadius.circular(4.0)), - shape: parseBoxShape(j["shape"]), - color: parseColor(theme, j["bgcolor"], - theme.brightness == Brightness.light ? Colors.grey[700] : Colors.white), - blendMode: parseBlendMode(j["blend_mode"]), - boxShadow: boxShadowsFromJSON(theme, j["box_shadow"]), - image: decorationImageFromJSON( - theme, j["image"], null), // TODO: replace null with PageArgsModel - ); + var decoration = parseBoxDecoration(value["decoration"], context); + decoration?.copyWith( + color: parseColor( + value["bgcolor"], + theme, + theme.brightness == Brightness.light + ? Colors.grey[700] + : Colors.white)!); return Tooltip( - message: j["message"], - enableFeedback: parseBool(j["enable_feedback"]), - enableTapToDismiss: parseBool(j["enable_tap_to_dismiss"], true)!, - excludeFromSemantics: parseBool(j["exclude_from_semantics"]), - height: parseDouble(j["height"]), - exitDuration: durationFromJSON(j["exit_duration"]), - preferBelow: parseBool(j["prefer_below"]), - padding: edgeInsetsFromJson(j["padding"]), + message: value["message"], + enableFeedback: parseBool(value["enable_feedback"]), + enableTapToDismiss: parseBool(value["enable_tap_to_dismiss"], true)!, + excludeFromSemantics: parseBool(value["exclude_from_semantics"]), + height: parseDouble(value["height"]), + exitDuration: parseDuration(value["exit_duration"]), + preferBelow: parseBool(value["prefer_below"]), + padding: parseEdgeInsets(value["padding"]), decoration: decoration, - textStyle: textStyleFromJson(theme, j["text_style"]), - verticalOffset: parseDouble(j["vertical_offset"]), - margin: edgeInsetsFromJson(j["margin"]), - textAlign: parseTextAlign(j["text_align"]), - showDuration: durationFromJSON(j["show_duration"]), - waitDuration: durationFromJSON(j["wait_duration"]), - triggerMode: parseTooltipTriggerMode(j["trigger_mode"]), + textStyle: parseTextStyle(value["text_style"], theme), + verticalOffset: parseDouble(value["vertical_offset"]), + margin: parseEdgeInsets(value["margin"]), + mouseCursor: parseMouseCursor(value["mouse_cursor"]), + textAlign: parseTextAlign(value["text_align"]), + showDuration: parseDuration(value["show_duration"]), + waitDuration: parseDuration(value["wait_duration"], defaultWaitDuration)!, + triggerMode: parseTooltipTriggerMode(value["trigger_mode"]), child: widget, ); } + +extension TooltipParsers on Control { + TooltipTriggerMode? getTooltipTriggerMode(String propertyName, + [TooltipTriggerMode? defaultValue]) { + return parseTooltipTriggerMode(get(propertyName), defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/transforms.dart b/packages/flet/lib/src/utils/transforms.dart index 02eb1491e..66e0bcccc 100644 --- a/packages/flet/lib/src/utils/transforms.dart +++ b/packages/flet/lib/src/utils/transforms.dart @@ -1,92 +1,42 @@ -import 'dart:convert'; - -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import '../models/control.dart'; import 'alignment.dart'; import 'numbers.dart'; -RotationDetails? parseRotate(Control control, String propName, +RotationDetails? parseRotationDetails(dynamic value, [RotationDetails? defaultValue]) { - var v = control.attrString(propName); - if (v == null) { - return defaultValue; - } - - final j1 = json.decode(v); - return rotateFromJSON(j1, defaultValue); -} - -RotationDetails rotateFromJSON(dynamic json, [RotationDetails? defaultValue]) { - if (json == null) { - return defaultValue!; - } - if (json is int || json is double) { + if (value == null) return defaultValue; + if (value is int || value is double) { return RotationDetails( - angle: parseDouble(json, 0)!, alignment: Alignment.center); + angle: parseDouble(value, 0)!, alignment: Alignment.center); } - return RotationDetails.fromJson(json); + return RotationDetails.fromJson(value); } -ScaleDetails? parseScale(Control control, String propName, - [ScaleDetails? defaultValue]) { - var v = control.attrString(propName); - if (v == null) { - return defaultValue; - } - - final j1 = json.decode(v); - return scaleFromJSON(j1, defaultValue); -} - -ScaleDetails? scaleFromJSON(dynamic json, [ScaleDetails? defaultValue]) { - if (json == null) { - return defaultValue; - } - if (json is int || json is double) { +ScaleDetails? parseScale(dynamic value, [ScaleDetails? defaultValue]) { + if (value == null) return defaultValue; + if (value is int || value is double) { return ScaleDetails( - scale: parseDouble(json), + scale: parseDouble(value), scaleX: null, scaleY: null, alignment: Alignment.center); } - return ScaleDetails.fromJson(json); -} - -Offset? parseOffset(Control control, String propName, [Offset? defaultValue]) { - var v = control.attrString(propName, null); - if (v == null) { - return defaultValue; - } - - final j1 = json.decode(v); - return offsetFromJSON(j1, defaultValue); + return ScaleDetails.fromJson(value); } -Offset? offsetFromJSON(dynamic json, [Offset? defaultValue]) { - if (json == null) { - return defaultValue; - } - var details = offsetDetailsFromJSON(json); +Offset? parseOffset(dynamic value, [Offset? defaultValue]) { + if (value == null) return defaultValue; + var details = OffsetDetails.fromValue(value); return Offset(details.x, details.y); } -List? parseOffsetList(Control control, String propName, - [List? defaultValue]) { - var v = control.attrString(propName, null); - if (v == null) { - return defaultValue; - } - - final j1 = json.decode(v); - return (j1 as List).map((e) => offsetFromJSON(e)).whereNotNull().toList(); -} - -OffsetDetails offsetDetailsFromJSON(dynamic json) { - return OffsetDetails.fromJson(json); +List? parseOffsetList(dynamic value, [List? defaultValue]) { + if (value == null) return defaultValue; + return (value as List).map((e) => parseOffset(e)).nonNulls.toList(); } class RotationDetails { @@ -95,10 +45,10 @@ class RotationDetails { RotationDetails({required this.angle, required this.alignment}); - factory RotationDetails.fromJson(Map json) { + factory RotationDetails.fromJson(Map value) { return RotationDetails( - angle: parseDouble(json["angle"], 0)!, - alignment: alignmentFromJson(json["alignment"], Alignment.center)!); + angle: parseDouble(value["angle"], 0)!, + alignment: parseAlignment(value["alignment"], Alignment.center)!); } } @@ -114,12 +64,12 @@ class ScaleDetails { required this.scaleY, required this.alignment}); - factory ScaleDetails.fromJson(Map json) { + factory ScaleDetails.fromJson(Map value) { return ScaleDetails( - scale: parseDouble(json["scale"]), - scaleX: parseDouble(json["scale_x"]), - scaleY: parseDouble(json["scale_y"]), - alignment: alignmentFromJson(json["alignment"], Alignment.center)!); + scale: parseDouble(value["scale"]), + scaleX: parseDouble(value["scale_x"]), + scaleY: parseDouble(value["scale_y"]), + alignment: parseAlignment(value["alignment"], Alignment.center)!); } } @@ -129,13 +79,33 @@ class OffsetDetails { OffsetDetails({required this.x, required this.y}); - factory OffsetDetails.fromJson(dynamic json) { - if (json is List && json.length > 1) { + factory OffsetDetails.fromValue(dynamic value) { + if (value is List && value.length > 1) { return OffsetDetails( - x: parseDouble(json[0], 0)!, y: parseDouble(json[1], 0)!); + x: parseDouble(value[0], 0)!, y: parseDouble(value[1], 0)!); } else { return OffsetDetails( - x: parseDouble(json["x"], 0)!, y: parseDouble(json["y"], 0)!); + x: parseDouble(value["x"], 0)!, y: parseDouble(value["y"], 0)!); } } } + +extension TransformParsers on Control { + RotationDetails? getRotationDetails(String propertyName, + [RotationDetails? defaultValue]) { + return parseRotationDetails(get(propertyName), defaultValue); + } + + ScaleDetails? getScale(String propertyName, [ScaleDetails? defaultValue]) { + return parseScale(get(propertyName), defaultValue); + } + + Offset? getOffset(String propertyName, [Offset? defaultValue]) { + return parseOffset(get(propertyName), defaultValue); + } + + List? getOffsetList(String propertyName, + [List? defaultValue]) { + return parseOffsetList(get(propertyName), defaultValue); + } +} diff --git a/packages/flet/lib/src/utils/uri.dart b/packages/flet/lib/src/utils/uri.dart index f0724a0e5..2ad5296a9 100644 --- a/packages/flet/lib/src/utils/uri.dart +++ b/packages/flet/lib/src/utils/uri.dart @@ -1,7 +1,7 @@ import 'strings.dart'; String getWebPageName(Uri uri) { - var urlPath = trim(uri.path, "/"); + var urlPath = uri.path.trimSymbol("/"); if (urlPath != "") { var pathParts = urlPath.split("/"); if (pathParts.length > 1) { @@ -27,7 +27,6 @@ bool isLocalhost(Uri uri) { return uri.host == "localhost" || uri.host == "127.0.0.1"; } -bool isUdsPath(String address) { - var uri = Uri.tryParse(address); - return uri == null || !uri.hasScheme; +bool isUdsPath(Uri address) { + return !address.hasScheme; } diff --git a/packages/flet/lib/src/utils/user_fonts.dart b/packages/flet/lib/src/utils/user_fonts.dart index 9f4b4b15a..ec4c5bb3f 100644 --- a/packages/flet/lib/src/utils/user_fonts.dart +++ b/packages/flet/lib/src/utils/user_fonts.dart @@ -1,38 +1,34 @@ -import 'dart:convert'; - import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; import '../models/control.dart'; - -import 'user_fonts_io.dart' if (dart.library.js) "user_fonts_web.dart"; +import 'user_fonts_web.dart' if (dart.library.io) "user_fonts_io.dart"; class UserFonts { static Map fontLoaders = {}; - static void loadFontFromUrl(String fontFamily, String fontUrl) { - debugPrint("Load font from URL: $fontUrl"); + static Future loadFontFromUrl(String fontFamily, String fontUrl) async { var key = "$fontFamily$fontUrl"; - if (fontLoaders.containsKey(key)) { - return; - } + if (fontLoaders.containsKey(key)) return; + debugPrint("Load font from URL: $fontUrl"); var fontLoader = FontLoader(fontFamily); fontLoaders[key] = fontLoader; fontLoader.addFont(fetchFontFromUrl(fontUrl)); - fontLoader.load(); + await fontLoader.load(); + debugPrint("Font loaded from URL: $fontUrl"); } - static void loadFontFromFile(String fontFamily, String fontPath) { - debugPrint("Load font from file: $fontPath"); + static Future loadFontFromFile( + String fontFamily, String fontPath) async { var key = "$fontFamily$fontPath"; - if (fontLoaders.containsKey(key)) { - return; - } + if (fontLoaders.containsKey(key)) return; + debugPrint("Load font from file: $fontPath"); var fontLoader = FontLoader(fontFamily); fontLoaders[key] = fontLoader; fontLoader.addFont(fetchFontFromFile(fontPath)); - fontLoader.load(); + await fontLoader.load(); + debugPrint("Font loaded from file: $fontPath"); } static Future fetchFontFromUrl(String url) async { @@ -47,16 +43,15 @@ class UserFonts { } } -Map parseFonts(Control control, String propName) { - var v = control.attrString(propName, null); - if (v == null) { - return {}; - } - - final j1 = json.decode(v); - return fontsFromJson(j1); +Map? parseFonts(dynamic value, + [Map? defaultValue]) { + if (value == null) return {}; + return Map.from(value); } -Map fontsFromJson(Map json) { - return json.map((key, value) => MapEntry(key, value)); +extension UserFontParsers on Control { + Map? getFonts(String propertyName, + [Map? defaultValue]) { + return parseFonts(get(propertyName), defaultValue); + } } diff --git a/packages/flet/lib/src/utils/weak_value_map.dart b/packages/flet/lib/src/utils/weak_value_map.dart new file mode 100644 index 000000000..ff4efb3b7 --- /dev/null +++ b/packages/flet/lib/src/utils/weak_value_map.dart @@ -0,0 +1,48 @@ +import 'dart:core'; + +import 'package:flutter/material.dart'; + +class WeakValueMap { + final Map> _map = {}; + late final Finalizer _finalizer; + + WeakValueMap() { + _finalizer = Finalizer((key) { + debugPrint("WeakValueMap.finalizer: $key"); + _map.remove(key); + }); + } + + void set(K key, V value) { + // Detach old value if present + final existing = _map[key]; + if (existing != null) { + _finalizer.detach(existing.detachToken); + } + + final tracked = _TrackedValue(value); + _map[key] = tracked; + + // Attach finalizer to the actual object, using a unique token + _finalizer.attach(value, key, detach: tracked.detachToken); + } + + V? get(K key) => _map[key]?.ref.target; + + void remove(K key) { + debugPrint("WeakValueMap.remove: $key"); + final tracked = _map.remove(key); + if (tracked != null) { + _finalizer.detach(tracked.detachToken); + } + } + + int get length => _map.length; +} + +class _TrackedValue { + final WeakReference ref; + final Object detachToken = Object(); + + _TrackedValue(V value) : ref = WeakReference(value); +} diff --git a/packages/flet/lib/src/utils/window.dart b/packages/flet/lib/src/utils/window.dart new file mode 100644 index 000000000..b969c07c0 --- /dev/null +++ b/packages/flet/lib/src/utils/window.dart @@ -0,0 +1,18 @@ +import 'package:collection/collection.dart'; +import 'package:window_manager/window_manager.dart'; + +import '../models/control.dart'; + +ResizeEdge? parseWindowResizeEdge(String? value, [ResizeEdge? defaultValue]) { + if (value == null) return defaultValue; + return ResizeEdge.values.firstWhereOrNull( + (e) => e.name.toLowerCase() == value.toLowerCase()) ?? + defaultValue; +} + +extension WindowParsers on Control { + ResizeEdge? getWindowResizeEdge(String propertyName, + [ResizeEdge? defaultValue]) { + return parseWindowResizeEdge(get(propertyName), defaultValue); + } +} diff --git a/packages/flet/lib/src/widgets/control_inherited_notifier.dart b/packages/flet/lib/src/widgets/control_inherited_notifier.dart new file mode 100644 index 000000000..8357f2b95 --- /dev/null +++ b/packages/flet/lib/src/widgets/control_inherited_notifier.dart @@ -0,0 +1,23 @@ +import 'package:flutter/widgets.dart'; + +import '../models/control.dart'; + +/// InheritedNotifier for Control. +class ControlInheritedNotifier extends InheritedNotifier { + const ControlInheritedNotifier({ + super.key, + super.notifier, + required super.child, + }) : super(); + + static Control? of(BuildContext context) { + return context + .dependOnInheritedWidgetOfExactType() + ?.notifier; + } + + @override + bool updateShouldNotify(ControlInheritedNotifier oldWidget) { + return notifier != oldWidget.notifier; + } +} diff --git a/packages/flet/lib/src/controls/error.dart b/packages/flet/lib/src/widgets/error.dart similarity index 94% rename from packages/flet/lib/src/controls/error.dart rename to packages/flet/lib/src/widgets/error.dart index 6ba848062..d1037d3df 100644 --- a/packages/flet/lib/src/controls/error.dart +++ b/packages/flet/lib/src/widgets/error.dart @@ -8,7 +8,7 @@ class ErrorControl extends StatelessWidget { @override Widget build(BuildContext context) { - debugPrint("ErrorControl build"); + debugPrint("ErrorControl build: $message"); List lines = [ Text(message, style: const TextStyle(color: Colors.white, fontSize: 12)) ]; diff --git a/packages/flet/lib/src/widgets/flet_store_mixin.dart b/packages/flet/lib/src/widgets/flet_store_mixin.dart new file mode 100644 index 000000000..bf4df8500 --- /dev/null +++ b/packages/flet/lib/src/widgets/flet_store_mixin.dart @@ -0,0 +1,22 @@ +import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; + +import '../flet_backend.dart'; +import '../models/page_size_view_model.dart'; + +mixin FletStoreMixin { + Widget withPageSize(Widget Function(BuildContext, PageSizeViewModel) build) { + return Selector( + selector: (_, backend) => PageSizeViewModel( + size: backend.pageSize, breakpoints: backend.sizeBreakpoints), + builder: (context, view, _) => build(context, view), + ); + } + + Widget withPagePlatform(Widget Function(BuildContext, TargetPlatform) build) { + return Selector( + selector: (_, backend) => backend.platform, + builder: (context, platform, _) => build(context, platform), + ); + } +} diff --git a/packages/flet/lib/src/widgets/loading_page.dart b/packages/flet/lib/src/widgets/loading_page.dart index bbec144b8..34e90d045 100644 --- a/packages/flet/lib/src/widgets/loading_page.dart +++ b/packages/flet/lib/src/widgets/loading_page.dart @@ -48,14 +48,15 @@ class LoadingPage extends StatelessWidget { color: theme.colorScheme.onErrorContainer, size: 30), const SizedBox(width: 8), Flexible( - child: Text( + child: SelectionArea( + child: Text( message, softWrap: true, style: Theme.of(context) .textTheme .bodySmall! .copyWith(color: theme.colorScheme.onErrorContainer), - )) + ))) ]), ); } diff --git a/packages/flet/lib/src/widgets/page_context.dart b/packages/flet/lib/src/widgets/page_context.dart new file mode 100644 index 000000000..39a8f9ce4 --- /dev/null +++ b/packages/flet/lib/src/widgets/page_context.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +import '../models/page_design.dart'; + +class PageContext extends InheritedWidget { + final PageDesign widgetsDesign; + final ThemeMode? themeMode; + final Brightness? brightness; + + const PageContext( + {required this.themeMode, + required this.brightness, + required this.widgetsDesign, + required super.child, + super.key}); + + @override + bool updateShouldNotify(covariant PageContext oldWidget) { + return themeMode != oldWidget.themeMode || + brightness != oldWidget.brightness || + widgetsDesign != oldWidget.widgetsDesign; + } + + static PageContext? of(BuildContext context) => + context.dependOnInheritedWidgetOfExactType(); +} diff --git a/packages/flet/lib/src/widgets/page_media.dart b/packages/flet/lib/src/widgets/page_media.dart index 846c03770..ca2dcec28 100644 --- a/packages/flet/lib/src/widgets/page_media.dart +++ b/packages/flet/lib/src/widgets/page_media.dart @@ -1,13 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_redux/flutter_redux.dart'; -import '../actions.dart'; -import '../flet_app_services.dart'; -import '../models/app_state.dart'; -import '../models/page_media_view_model.dart'; +import '../flet_backend.dart'; import '../protocol/page_media_data.dart'; import '../utils/debouncer.dart'; -import '../utils/desktop.dart'; class PageMedia extends StatefulWidget { const PageMedia({super.key}); @@ -17,8 +12,7 @@ class PageMedia extends StatefulWidget { } class _PageMediaState extends State { - final _debouncer = Debouncer(milliseconds: 250); - int _pageMediaTimestamp = 0; + final _debouncer = Debouncer(milliseconds: 100); @override void dispose() { @@ -26,70 +20,56 @@ class _PageMediaState extends State { super.dispose(); } - _onScreenSizeChanged(bool isRegistered, Size newSize, Function dispatch) { - if (isRegistered) { + _onPageSizeChanged(bool pageSizeUpdated, Size newSize) { + var backend = FletBackend.of(context); + if (pageSizeUpdated) { _debouncer.run(() { - debugPrint("Send current size to reducer: $newSize"); - getWindowMediaData().then((wmd) { - dispatch(PageSizeChangeAction( - newSize, wmd, FletAppServices.of(context).server)); - }); + backend.updatePageSize(newSize); }); } else { - dispatch(PageSizeChangeAction( - newSize, null, FletAppServices.of(context).server)); + backend.updatePageSize(newSize); } } - _onScreenBrightnessChanged(Brightness brightness, Function dispatch) { - debugPrint("Send new brightness to reducer: $brightness"); - dispatch(PageBrightnessChangeAction( - brightness, FletAppServices.of(context).server)); + _onPlatformBrightnessChanged(Brightness newBrightness) { + FletBackend.of(context).updateBrightness(newBrightness); } - _onMediaChanged(PageMediaData media, Function dispatch) { - var now = DateTime.now().millisecondsSinceEpoch; - if (now - _pageMediaTimestamp > 50) { - _pageMediaTimestamp = now; - debugPrint("Send new page media data to reducer: $media"); - dispatch( - PageMediaChangeAction(media, FletAppServices.of(context).server)); - } + _onMediaChanged(PageMediaData newMedia) { + FletBackend.of(context).updateMedia(newMedia); } @override Widget build(BuildContext context) { - debugPrint("Page media build"); + FletBackend backend = FletBackend.of(context); + + WidgetsBinding.instance.addPostFrameCallback((_) { + var pageSizeUpdated = backend.pageSizeUpdated.isCompleted; + + var platformBrightness = MediaQuery.platformBrightnessOf(context); + if (platformBrightness != backend.platformBrightness || + !pageSizeUpdated) { + _onPlatformBrightnessChanged(platformBrightness); + } + + var padding = MediaQuery.paddingOf(context); + var viewPadding = MediaQuery.viewPaddingOf(context); + var viewInsets = MediaQuery.viewInsetsOf(context); + var newMedia = PageMediaData( + padding: PaddingData(padding), + viewPadding: PaddingData(viewPadding), + viewInsets: PaddingData(viewInsets)); - return StoreConnector( - distinct: true, - converter: (store) => PageMediaViewModel.fromStore(store), - builder: (context, viewModel) { - MediaQueryData media = MediaQuery.of(context); - if (media.size != viewModel.size) { - _onScreenSizeChanged( - viewModel.isRegistered, media.size, viewModel.dispatch); - } else { - debugPrint("Page size did not change."); - } - if (media.platformBrightness != viewModel.displayBrightness) { - _onScreenBrightnessChanged( - media.platformBrightness, viewModel.dispatch); - } else { - debugPrint("Page brightness did not change."); - } + if (newMedia != backend.media || !pageSizeUpdated) { + _onMediaChanged(newMedia); + } - var newMdeia = PageMediaData( - padding: PaddingData(media.padding), - viewPadding: PaddingData(media.viewPadding), - viewInsets: PaddingData(media.viewInsets)); + var pageSize = MediaQuery.sizeOf(context); + if (pageSize != backend.pageSize) { + _onPageSizeChanged(pageSizeUpdated, pageSize); + } + }); - if (newMdeia != viewModel.media) { - _onMediaChanged(newMdeia, viewModel.dispatch); - } else { - debugPrint("Page media data did not change."); - } - return const SizedBox.shrink(); - }); + return const SizedBox.shrink(); } } diff --git a/packages/flet/lib/src/widgets/radio_group_provider.dart b/packages/flet/lib/src/widgets/radio_group_provider.dart new file mode 100644 index 000000000..0baded8dd --- /dev/null +++ b/packages/flet/lib/src/widgets/radio_group_provider.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +import '../models/control.dart'; + +class RadioGroupProvider extends InheritedWidget { + final Control radioGroupControl; + + const RadioGroupProvider({ + super.key, + required this.radioGroupControl, + required super.child, + }); + + static Control? of(BuildContext context) { + return context + .dependOnInheritedWidgetOfExactType() + ?.radioGroupControl; + } + + @override + bool updateShouldNotify(RadioGroupProvider oldWidget) { + return oldWidget.radioGroupControl != radioGroupControl; + } +} diff --git a/packages/flet/lib/src/widgets/scaffold_key_provider.dart b/packages/flet/lib/src/widgets/scaffold_key_provider.dart new file mode 100644 index 000000000..926e2255e --- /dev/null +++ b/packages/flet/lib/src/widgets/scaffold_key_provider.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class ScaffoldKeyProvider extends InheritedWidget { + final GlobalKey scaffoldKey; + + const ScaffoldKeyProvider({ + super.key, + required this.scaffoldKey, + required super.child, + }); + + static GlobalKey? of(BuildContext context) { + return context + .dependOnInheritedWidgetOfExactType() + ?.scaffoldKey; + } + + @override + bool updateShouldNotify(ScaffoldKeyProvider oldWidget) { + return oldWidget.scaffoldKey != scaffoldKey; + } +} diff --git a/packages/flet/lib/src/widgets/skip_inherited_notifier.dart b/packages/flet/lib/src/widgets/skip_inherited_notifier.dart new file mode 100644 index 000000000..762220d6c --- /dev/null +++ b/packages/flet/lib/src/widgets/skip_inherited_notifier.dart @@ -0,0 +1 @@ +abstract class SkipInheritedNotifier {} diff --git a/packages/flet/lib/src/widgets/window_media.dart b/packages/flet/lib/src/widgets/window_media.dart deleted file mode 100644 index 750b7ceec..000000000 --- a/packages/flet/lib/src/widgets/window_media.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:window_manager/window_manager.dart'; - -import '../actions.dart'; -import '../flet_app_services.dart'; -import '../utils/desktop.dart'; - -class WindowMedia extends StatefulWidget { - final dynamic dispatch; - const WindowMedia({super.key, required this.dispatch}); - - @override - // ignore: library_private_types_in_public_api - WindowMediaState createState() => WindowMediaState(); -} - -class WindowMediaState extends State with WindowListener { - @override - void initState() { - super.initState(); - windowManager.addListener(this); - } - - @override - void dispose() { - windowManager.removeListener(this); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return const SizedBox.shrink(); - } - - @override - void onWindowEvent(String eventName) { - //debugPrint('START [WindowManager] onWindowEvent: $eventName'); - - if (eventName == "resize" || eventName == "move") { - return; - } - - debugPrint('[WindowManager] onWindowEvent: $eventName'); - getWindowMediaData().then((wmd) { - debugPrint("WindowMediaData: $wmd"); - widget.dispatch!(WindowEventAction( - eventName, wmd, FletAppServices.of(context).server)); - }); - } -} diff --git a/packages/flet/pubspec.yaml b/packages/flet/pubspec.yaml index 2131fba6a..ee53cfa36 100644 --- a/packages/flet/pubspec.yaml +++ b/packages/flet/pubspec.yaml @@ -4,7 +4,7 @@ homepage: https://flet.dev repository: https://github.com/flet-dev/flet/packages/flet version: 0.70.0 -# This package supports all platforms listed below. +# Supported platforms platforms: android: ios: @@ -22,27 +22,26 @@ dependencies: flutter_localizations: sdk: flutter - redux: ^5.0.0 - flutter_redux: ^0.10.0 equatable: ^2.0.3 - web_socket_channel: ^2.4.0 - window_manager: ^0.4.3 - http: ^1.2.2 - collection: ^1.16.0 - url_launcher: ^6.3.1 - flutter_markdown: ^0.7.4+1 - flutter_highlight: ^0.7.0 - highlight: ^0.7.0 - markdown: ^7.2.2 - file_picker: ^10.1.9 - shared_preferences: ^2.3.2 - flutter_svg: ^2.0.13 + provider: ^6.1.2 + web_socket_channel: ^3.0.2 + msgpack_dart: ^1.0.1 + window_manager: ^0.5.0 window_to_front: ^0.0.3 - sensors_plus: ^4.0.2 - path: ^1.8.2 - js: ^0.6.5 - fl_chart: ^0.69.0 + collection: ^1.19.0 + flutter_svg: 2.1.0 + path: ^1.9.0 + url_launcher: 6.3.1 + http: 1.3.0 + shared_preferences: 2.5.2 device_info_plus: ^11.2.0 + flutter_markdown: 0.7.6+2 + flutter_highlight: 0.7.0 + markdown: 7.3.0 + sensors_plus: ^6.1.1 + web: ^1.1.1 + file_picker: ^10.1.9 + path_provider: ^2.1.5 dev_dependencies: flutter_test: diff --git a/packages/flet/test/controls/control_test.dart b/packages/flet/test/controls/control_test.dart deleted file mode 100644 index ae83a5911..000000000 --- a/packages/flet/test/controls/control_test.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'dart:convert'; - -import 'package:flet/src/models/control.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test("Two controls are equal", () { - Control c1 = Control( - id: "i1", - pid: "p1", - type: "stack", - name: null, - childIds: const ["txt1", "btn1"], - attrs: const {"text": "Hello!", "width": "200"}); - - Control c2 = Control( - id: "i1", - pid: "p1", - type: "stack", - name: null, - childIds: const ["txt1", "btn1"], - attrs: const {"width": "200", "text": "Hello!"}); - - expect(c1 == c2, true); - }); - - test('Deserialize control', () { - const t1 = '''{ - "t": "stack", - "p": "", - "i": "s1", - "n": "content", - "c": ["txt1", "txt2"], - "at": "0", - "value": "Hello, world!" - }'''; - - final j1 = json.decode(t1); - final c1 = Control.fromJson(j1); - - expect(c1.id, "s1"); - expect(c1.pid, ""); - expect(c1.type, "stack"); - expect(c1.name, "content"); - expect(c1.childIds, ["txt1", "txt2"]); - expect(c1.attrs.length, 2); - expect(c1.attrs["at"], "0"); - expect(c1.attrs["value"], "Hello, world!"); - }); -} diff --git a/packages/flet/test/models/linechart_event_data_test.dart b/packages/flet/test/models/linechart_event_data_test.dart deleted file mode 100644 index 9cff525e2..000000000 --- a/packages/flet/test/models/linechart_event_data_test.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'dart:convert'; - -import 'package:flet/src/controls/linechart.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test("Test LinechartEventData equality", () { - var e1 = const LineChartEventData(eventType: "Hover", barSpots: [ - LineChartEventDataSpot(barIndex: 0, spotIndex: 1), - LineChartEventDataSpot(barIndex: 1, spotIndex: 1) - ]); - var e2 = const LineChartEventData(eventType: "Hover", barSpots: [ - LineChartEventDataSpot(barIndex: 0, spotIndex: 1), - LineChartEventDataSpot(barIndex: 1, spotIndex: 1) - ]); - expect(e1 == e2, true); - }); - - test("Test LinechartEventData inequality", () { - var e1 = const LineChartEventData(eventType: "Hover", barSpots: [ - LineChartEventDataSpot(barIndex: 0, spotIndex: 0), - LineChartEventDataSpot(barIndex: 1, spotIndex: 0) - ]); - var e2 = const LineChartEventData(eventType: "Hover", barSpots: [ - LineChartEventDataSpot(barIndex: 0, spotIndex: 1), - LineChartEventDataSpot(barIndex: 1, spotIndex: 1) - ]); - expect(e1 == e2, false); - }); - - test("LinechartEventData serialize to json", () { - var e = const LineChartEventData(eventType: "Hover", barSpots: [ - LineChartEventDataSpot(barIndex: 0, spotIndex: 1), - LineChartEventDataSpot(barIndex: 1, spotIndex: 1) - ]); - - final j = json.encode(e); - - expect(j, - '{"type":"Hover","spots":[{"bar_index":0,"spot_index":1},{"bar_index":1,"spot_index":1}]}'); - }); -} diff --git a/packages/flet/test/protocol/add_page_controls_payload_test.dart b/packages/flet/test/protocol/add_page_controls_payload_test.dart deleted file mode 100644 index 0ecaf5bfc..000000000 --- a/packages/flet/test/protocol/add_page_controls_payload_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flet/src/protocol/add_page_controls_payload.dart'; - -void main() { - test("AddPageControlsPayload payload deserialized", () { - const myJsonAsString = '''{ - "trimIDs": ["c1", "c2"], - "controls": [ - { - "i": "txt1", - "t": "text", - "p": "page", - "c": [], - "value": "Text A" - }, - { - "i": "stack1", - "t": "stack", - "p": "page", - "c": ["txt2"], - "align": "center" - } - ]}'''; - - final s = AddPageControlsPayload.fromJson(json.decode(myJsonAsString)); - expect(s.trimIDs.length, 2); - expect(s.controls.length, 2); - }); -} diff --git a/packages/flet/test/protocol/app_become_inactive_payload_test.dart b/packages/flet/test/protocol/app_become_inactive_payload_test.dart deleted file mode 100644 index 80c3fda57..000000000 --- a/packages/flet/test/protocol/app_become_inactive_payload_test.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flet/src/protocol/app_become_inactive_payload.dart'; - -void main() { - test("AppBecomeInactivePayload payload deserialized", () { - const myJsonAsString = '''{ - "message": "Application is inactive." - }'''; - - final s = AppBecomeInactivePayload.fromJson(json.decode(myJsonAsString)); - expect(s.message, "Application is inactive."); - }); -} diff --git a/packages/flet/test/protocol/append_control_props_payload_test.dart b/packages/flet/test/protocol/append_control_props_payload_test.dart deleted file mode 100644 index 344f27b2f..000000000 --- a/packages/flet/test/protocol/append_control_props_payload_test.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flet/src/protocol/append_control_props_request.dart'; - -void main() { - test("AppendControlPropsPayload payload deserialized", () { - const myJsonAsString = '''{ - "props": [ - { - "i": "txt1", - "value": "Text A" - }, - { - "i": "stack1", - "align": "center" - } - ]}'''; - - final s = AppendControlPropsPayload.fromJson(json.decode(myJsonAsString)); - expect(s.props.length, 2); - }); -} diff --git a/packages/flet/test/protocol/clean_control_payload_test.dart b/packages/flet/test/protocol/clean_control_payload_test.dart deleted file mode 100644 index a2141592b..000000000 --- a/packages/flet/test/protocol/clean_control_payload_test.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flet/src/protocol/clean_control_payload.dart'; - -void main() { - test("CleanControlPayload payload deserialized", () { - const myJsonAsString = '''{ - "ids": ["c1", "c2"] - }'''; - - final s = CleanControlPayload.fromJson(json.decode(myJsonAsString)); - expect(s.ids.length, 2); - }); -} diff --git a/packages/flet/test/protocol/message_test.dart b/packages/flet/test/protocol/message_test.dart deleted file mode 100644 index 6c49345a1..000000000 --- a/packages/flet/test/protocol/message_test.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flet/src/protocol/message.dart'; -import 'package:flet/src/protocol/remove_control_payload.dart'; - -void main() { - test("Message parse from JSON", () { - const s = ''' - { - "action": "removeControl", - "payload": { - "ids": ["i1", "i2"] - } - } -'''; - - final m = Message.fromJson(json.decode(s)); - expect(m.action, MessageAction.removeControl); - - if (m.action == MessageAction.removeControl) { - final payload = RemoveControlPayload.fromJson(m.payload); - expect(payload.ids.length, 2); - } - }); -} diff --git a/packages/flet/test/protocol/page_controls_batch_payload_test.dart b/packages/flet/test/protocol/page_controls_batch_payload_test.dart deleted file mode 100644 index f9a15494c..000000000 --- a/packages/flet/test/protocol/page_controls_batch_payload_test.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'dart:convert'; - -import 'package:flet/src/protocol/page_controls_batch_payload.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test("PageControlsBatchPayload serialize to message", () { - const myJsonAsString = '''[ - {"action":"updateControlProps","payload":{"props":[{"i":"page","width":"100","height":"200"},{"i":"txt1","value":"Hello, world!"}]}}, - {"action":"removeControl","payload":{"ids":["a1", "b2"]}} - ] - '''; - - final s = PageControlsBatchPayload.fromJson(json.decode(myJsonAsString)); - expect(s.messages.length, 2); - }); -} diff --git a/packages/flet/test/protocol/page_event_from_web_request_test.dart b/packages/flet/test/protocol/page_event_from_web_request_test.dart deleted file mode 100644 index 410a46525..000000000 --- a/packages/flet/test/protocol/page_event_from_web_request_test.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flet/src/protocol/message.dart'; -import 'package:flet/src/protocol/page_event_from_web_request.dart'; - -void main() { - test("PageEventFromWebRequest serialize to message", () { - final m = Message( - action: MessageAction.pageEventFromWeb, - payload: PageEventFromWebRequest( - eventTarget: "page", eventName: "resize", eventData: "100x200")); - - final j = json.encode(m); - - expect(j, - '{"action":"pageEventFromWeb","payload":{"eventTarget":"page","eventName":"resize","eventData":"100x200"}}'); - }); -} diff --git a/packages/flet/test/protocol/register_webclient_payload_test.dart b/packages/flet/test/protocol/register_webclient_payload_test.dart deleted file mode 100644 index 255aa33d4..000000000 --- a/packages/flet/test/protocol/register_webclient_payload_test.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flet/src/protocol/session_payload.dart'; - -void main() { - test("Session payload deserialized", () { - const myJsonAsString = '''{ - "id": "session-1234", - "controls": { - "page": { - "t": "page", - "p": "", - "i": "page", - "c": [] - } - }}'''; - - final s = SessionPayload.fromJson(json.decode(myJsonAsString)); - expect(s.id, 'session-1234'); - }); -} diff --git a/packages/flet/test/protocol/register_webclient_request_test.dart b/packages/flet/test/protocol/register_webclient_request_test.dart deleted file mode 100644 index 278637896..000000000 --- a/packages/flet/test/protocol/register_webclient_request_test.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'dart:convert'; - -import 'package:flet/src/protocol/message.dart'; -import 'package:flet/src/protocol/register_webclient_request.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test("RegisterWebClientRequest serialize to message", () { - final m = Message( - action: MessageAction.registerWebClient, - payload: RegisterWebClientRequest(pageName: "test-page1")); - - final j = json.encode(m); - expect(j, - '{"action":"registerWebClient","payload":{"pageName":"test-page1","pageRoute":null,"pageWidth":null,"pageHeight":null,"windowWidth":null,"windowHeight":null,"windowTop":null,"windowLeft":null,"isPWA":null,"isWeb":null,"isDebug":null,"platform":null,"platformBrightness":null,"media":null,"sessionId":null}}'); - }); -} diff --git a/packages/flet/test/protocol/register_webclient_response_test.dart b/packages/flet/test/protocol/register_webclient_response_test.dart deleted file mode 100644 index c34da0270..000000000 --- a/packages/flet/test/protocol/register_webclient_response_test.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'dart:convert'; - -import 'package:flet/src/protocol/register_webclient_response.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test("RegisterWebClientResponse with session deserialized", () { - const myJsonAsString = '''{ - "appInactive": false, - "session": { - "id": "session-1234", - "controls": { - "page": { - "i": "page", - "t": "page", - "p": "", - "c": ["txt1", "stack1"], - "hash": "aaa" - } - } - }}'''; - - final s = RegisterWebClientResponse.fromJson(json.decode(myJsonAsString)); - expect(s.session!.id, 'session-1234'); - expect(s.session!.controls.length, 1); - }); - - test("RegisterWebClientResponse with error deserialized", () { - const myJsonAsString = '''{ - "error": "Page does not exist", - "appInactive": false, - "session": null - }'''; - - final s = RegisterWebClientResponse.fromJson(json.decode(myJsonAsString)); - expect(s.session == null, true); - expect(s.error, "Page does not exist"); - }); -} diff --git a/packages/flet/test/protocol/remove_control_payload_test.dart b/packages/flet/test/protocol/remove_control_payload_test.dart deleted file mode 100644 index cdbfc76bd..000000000 --- a/packages/flet/test/protocol/remove_control_payload_test.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flet/src/protocol/remove_control_payload.dart'; - -void main() { - test("RemoveControlPayload payload deserialized", () { - const myJsonAsString = '''{ - "ids": ["c1", "c2"] - }'''; - - final s = RemoveControlPayload.fromJson(json.decode(myJsonAsString)); - expect(s.ids.length, 2); - }); -} diff --git a/packages/flet/test/protocol/replace_page_controls_payload_test.dart b/packages/flet/test/protocol/replace_page_controls_payload_test.dart deleted file mode 100644 index 5c584160a..000000000 --- a/packages/flet/test/protocol/replace_page_controls_payload_test.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flet/src/protocol/replace_page_controls_payload.dart'; - -void main() { - test("ReplacePageControlsPayload payload deserialized", () { - const myJsonAsString = '''{ - "remove": true, - "ids": ["c1", "c2"], - "controls": [ - { - "i": "txt1", - "t": "text", - "p": "page", - "c": [], - "value": "Text A" - }, - { - "i": "stack1", - "t": "stack", - "p": "page", - "c": ["txt2"], - "align": "center" - } - ]}'''; - - final s = ReplacePageControlsPayload.fromJson(json.decode(myJsonAsString)); - expect(s.ids.length, 2); - expect(s.controls.length, 2); - expect(s.remove, true); - }); -} diff --git a/packages/flet/test/protocol/session_crashed_payload_test.dart b/packages/flet/test/protocol/session_crashed_payload_test.dart deleted file mode 100644 index 2e85be44f..000000000 --- a/packages/flet/test/protocol/session_crashed_payload_test.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flet/src/protocol/session_crashed_payload.dart'; - -void main() { - test("SessionCrashedPayload payload deserialized", () { - const myJsonAsString = '''{ - "message": "Error while processing request!" - }'''; - - final s = SessionCrashedPayload.fromJson(json.decode(myJsonAsString)); - expect(s.message, "Error while processing request!"); - }); -} diff --git a/packages/flet/test/protocol/session_payload_test.dart b/packages/flet/test/protocol/session_payload_test.dart deleted file mode 100644 index 16bd52a8f..000000000 --- a/packages/flet/test/protocol/session_payload_test.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flet/src/protocol/session_payload.dart'; - -void main() { - test("Session payload deserialized", () { - const myJsonAsString = '''{ - "id": "session-1234", - "controls": { - "page": { - "i": "page", - "t": "page", - "p": "", - "c": ["txt1", "stack1"], - "hash": "aaa" - }, - "txt1": { - "i": "txt1", - "t": "text", - "p": "page", - "c": [], - "value": "Text A" - }, - "stack1": { - "i": "stack1", - "t": "stack", - "p": "page", - "c": ["txt2"], - "align": "center" - }, - "txt2": { - "i": "txt2", - "t": "text", - "p": "stack1", - "c": [], - "value": "Text B", - "color": "red" - } - }}'''; - - final s = SessionPayload.fromJson(json.decode(myJsonAsString)); - expect(s.id, 'session-1234'); - expect(s.controls.length, 4); - expect(s.controls['page']!.attrs.length, 1); - expect(s.controls['txt1']!.attrs.length, 1); - expect(s.controls['stack1']!.attrs.length, 1); - expect(s.controls['txt2']!.attrs.length, 2); - expect(s.controls['txt2']!.attrs["color"], "red"); - expect(s.controls['txt2']!.attrs["value"], "Text B"); - }); -} diff --git a/packages/flet/test/protocol/update_control_props_payload_test.dart b/packages/flet/test/protocol/update_control_props_payload_test.dart deleted file mode 100644 index 1b578c753..000000000 --- a/packages/flet/test/protocol/update_control_props_payload_test.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'dart:convert'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flet/src/protocol/update_control_props_payload.dart'; - -void main() { - test("UpdateControlPropsPayload payload deserialized", () { - const myJsonAsString = '''{ - "props": [ - { - "i": "txt1", - "value": "Text A" - }, - { - "i": "stack1", - "align": "center" - } - ]}'''; - - final s = UpdateControlPropsPayload.fromJson(json.decode(myJsonAsString)); - expect(s.props.length, 2); - }); -} diff --git a/packages/flet/test/protocol/update_control_props_request_test.dart b/packages/flet/test/protocol/update_control_props_request_test.dart deleted file mode 100644 index 4ef30d02c..000000000 --- a/packages/flet/test/protocol/update_control_props_request_test.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:convert'; - -import 'package:flet/src/protocol/message.dart'; -import 'package:flet/src/protocol/update_control_props_request.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test("UpdateControlPropsRequest serialize to message", () { - final m = Message( - action: MessageAction.updateControlProps, - payload: UpdateControlPropsRequest(props: [ - {"i": "page", "width": "100", "height": "200"}, - {"i": "txt1", "value": "Hello, world!"}, - ])); - - final j = json.encode(m); - - expect(j, - '{"action":"updateControlProps","payload":{"props":[{"i":"page","width":"100","height":"200"},{"i":"txt1","value":"Hello, world!"}]}}'); - }); -} diff --git a/packages/flet/test/utils/control_test.dart b/packages/flet/test/utils/control_test.dart new file mode 100644 index 000000000..7e7acedea --- /dev/null +++ b/packages/flet/test/utils/control_test.dart @@ -0,0 +1,99 @@ +import 'package:flet/flet.dart'; +import 'package:flutter_test/flutter_test.dart'; + +var backend = FletBackend( + pageUri: Uri.parse("uri"), assetsDir: "", extensions: [], multiView: false); + +void main() { + test("Both controls must be equal", () { + var c1 = Control( + id: 1, + type: "Button", + properties: { + "a": 1, + "b": 2, + "c": {"c_0": "test"} + }, + backend: backend); + var c2 = Control( + id: 1, + type: "Button", + properties: { + "a": 1, + "b": 2, + "c": {"c_0": "test"} + }, + backend: backend); + expect(c1 == c2, true); + }); + + test("Update control with a Map", () { + var c1 = Control( + id: 1, + type: "Button", + properties: { + "a": 1, + "b": 2, + "c": {"c_0": "test"} + }, + backend: backend); + bool changed = c1.update({ + "a": 10, + "d": true, + "c": {"c_0": "test_2", "sub_1": "something"} + }); + expect(changed, true); + expect(c1.properties["a"] == 10, true); + expect(c1.properties["b"] == 2, true); + expect(c1.properties["d"], true); + expect(c1.properties["c"]["c_0"] == "test_2", true); + expect(c1.properties["c"]["sub_1"] == "something", true); + }); + + test("updateControl did not change control", () { + var a1 = Control( + id: 1, + type: "Button", + properties: { + "a": 1, + "b": 2, + "c": {"c_0": "test"} + }, + backend: backend); + bool changed = a1.update({ + "a": 1, + "b": 2, + "c": {"c_0": "test"} + }); + expect(changed, false); + }); + + test("updateControl on 1st level changed control", () { + var a1 = Control( + id: 1, + type: "Button", + properties: { + "a": 1, + }, + backend: backend); + bool changed = a1.update({ + "a": 2, + }); + expect(changed, true); + }); + + test("updateControl on 2nd level changed control", () { + var a1 = Control( + id: 1, + type: "Button", + properties: { + "a": 1, + "c": {"c_0": "test"} + }, + backend: backend); + bool changed = a1.update({ + "c": {"c_0": "changed!"} + }); + expect(changed, true); + }); +} diff --git a/packages/flet/test/utils/theme_test.dart b/packages/flet/test/utils/theme_test.dart deleted file mode 100644 index 60e40ccb6..000000000 --- a/packages/flet/test/utils/theme_test.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'dart:convert'; - -import 'package:flet/src/utils/theme.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test("Light theme is parsed correctly from JSON", () { - const t1 = '''{ - "color_scheme_seed": "red", - "brightness": "light", - "use_material3": false - }'''; - - final j1 = json.decode(t1); - var theme = themeFromJson(j1, Brightness.light, null); - - expect(theme.brightness, Brightness.light); - expect(theme.useMaterial3, false); - expect(theme.primaryColor, const Color(0xff904a42)); - }); - - test("Dark theme is parsed correctly from JSON", () { - const t1 = '''{ - "color_scheme_seed": "cyan", - "brightness": "dark" - }'''; - - final j1 = json.decode(t1); - var theme = themeFromJson(j1, Brightness.dark, null); - - expect(theme.brightness, Brightness.dark); - expect(theme.useMaterial3, true); - expect(theme.primaryColor, const Color(0xff0e1416)); - }); -} diff --git a/packages/flet/test/utils/user_fonts_test.dart b/packages/flet/test/utils/user_fonts_test.dart index 649140611..3e21adc95 100644 --- a/packages/flet/test/utils/user_fonts_test.dart +++ b/packages/flet/test/utils/user_fonts_test.dart @@ -1,17 +1,14 @@ -import 'dart:convert'; - import 'package:flet/src/utils/user_fonts.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test("Custom fonts are parsed from JSON", () { - const t1 = '''{ - "font1": "https://fonts.com/font1.ttf", - "font2": "https://fonts.com/font2.ttf" - }'''; + const t1 = { + "font1": "https://fonts.com/font1.ttf", + "font2": "https://fonts.com/font2.ttf" + }; - final j1 = json.decode(t1); - var fonts = fontsFromJson(j1); + var fonts = parseFonts(t1)!; expect(fonts.length, 2); expect(fonts["font1"], "https://fonts.com/font1.ttf"); diff --git a/sdk/python/packages/flet-cli/pyproject.toml b/sdk/python/packages/flet-cli/pyproject.toml index a3b6e4643..b821027d5 100644 --- a/sdk/python/packages/flet-cli/pyproject.toml +++ b/sdk/python/packages/flet-cli/pyproject.toml @@ -5,7 +5,7 @@ description = "Flet CLI" authors = [{ name = "Appveyor Systems Inc.", email = "hello@flet.dev" }] license = "Apache-2.0" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "flet", "watchdog >=4.0.0", diff --git a/sdk/python/packages/flet-cli/src/flet_cli/__pyinstaller/rthooks/pyi_rth_localhost_fletd.py b/sdk/python/packages/flet-cli/src/flet_cli/__pyinstaller/rthooks/pyi_rth_localhost_fletd.py index f901f0028..07bf97d3b 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/__pyinstaller/rthooks/pyi_rth_localhost_fletd.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/__pyinstaller/rthooks/pyi_rth_localhost_fletd.py @@ -1,9 +1,7 @@ import logging import os -import flet - -logger = logging.getLogger(flet.__name__) +logger = logging.getLogger("flet") logger.info("Running PyInstaller runtime hook for Flet...") diff --git a/sdk/python/packages/flet-cli/src/flet_cli/commands/build.py b/sdk/python/packages/flet-cli/src/flet_cli/commands/build.py index d951bde13..1b9f175bf 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/commands/build.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/commands/build.py @@ -32,10 +32,10 @@ from rich.table import Column, Table from rich.theme import Theme -PYODIDE_ROOT_URL = "https://cdn.jsdelivr.net/pyodide/v0.27.2/full" +PYODIDE_ROOT_URL = "https://cdn.jsdelivr.net/pyodide/v0.27.7/full" DEFAULT_TEMPLATE_URL = "gh:flet-dev/flet-build-template" -MINIMAL_FLUTTER_VERSION = version.Version("3.29.3") +MINIMAL_FLUTTER_VERSION = version.Version("3.32.4") no_rich_output = get_bool_env_var("FLET_CLI_NO_RICH_OUTPUT") @@ -154,8 +154,10 @@ def __init__(self, parser: argparse.ArgumentParser) -> None: self.cross_platform_permissions = { "location": { "info_plist": { - "NSLocationWhenInUseUsageDescription": "This app uses location service when in use.", - "NSLocationAlwaysAndWhenInUseUsageDescription": "This app uses location service.", + "NSLocationWhenInUseUsageDescription": "This app uses location " + "service when in use.", + "NSLocationAlwaysAndWhenInUseUsageDescription": "This app uses " + "location service.", }, "macos_entitlements": { "com.apple.security.personal-information.location": True @@ -172,7 +174,8 @@ def __init__(self, parser: argparse.ArgumentParser) -> None: }, "camera": { "info_plist": { - "NSCameraUsageDescription": "This app uses the camera to capture photos and videos." + "NSCameraUsageDescription": "This app uses the camera to capture " + "photos and videos." }, "macos_entitlements": {"com.apple.security.device.camera": True}, "android_permissions": {"android.permission.CAMERA": True}, @@ -186,7 +189,8 @@ def __init__(self, parser: argparse.ArgumentParser) -> None: }, "microphone": { "info_plist": { - "NSMicrophoneUsageDescription": "This app uses microphone to record sounds.", + "NSMicrophoneUsageDescription": "This app uses microphone to " + "record sounds.", }, "macos_entitlements": {"com.apple.security.device.audio-input": True}, "android_permissions": { @@ -198,7 +202,8 @@ def __init__(self, parser: argparse.ArgumentParser) -> None: }, "photo_library": { "info_plist": { - "NSPhotoLibraryUsageDescription": "This app saves photos and videos to the photo library." + "NSPhotoLibraryUsageDescription": "This app saves photos and " + "videos to the photo library." }, "macos_entitlements": { "com.apple.security.personal-information.photos-library": True @@ -243,7 +248,8 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: dest="target_arch", nargs="+", default=[], - help="package for specific architectures only. Used with Android and macOS builds only.", + help="package for specific architectures only. Used with Android and macOS " + "builds only.", ) parser.add_argument( "--exclude", @@ -256,7 +262,8 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: "-o", "--output", dest="output_dir", - help="where to put resulting executable or bundle (default is /build/)", + help="where to put resulting executable or bundle (default is " + "/build/)", required=False, ) parser.add_argument( @@ -281,19 +288,22 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: parser.add_argument( "--product", dest="product_name", - help="project display name that is shown in window titles and about app dialogs", + help="project display name that is shown in window titles and about " + "app dialogs", required=False, ) parser.add_argument( "--org", dest="org_name", - help='org name in reverse domain name notation, e.g. "com.mycompany" - combined with project name and used as an iOS and Android bundle ID', + help='org name in reverse domain name notation, e.g. "com.mycompany" - "' + '"combined with project name and used as an iOS and Android bundle ID', required=False, ) parser.add_argument( "--bundle-id", dest="bundle_id", - help='bundle ID for the application, e.g. "com.mycompany.app-name" - used as an iOS, Android, macOS and Linux bundle ID', + help='bundle ID for the application, e.g. "com.mycompany.app-name" - used ' + "as an iOS, Android, macOS and Linux bundle ID", required=False, ) parser.add_argument( @@ -311,7 +321,8 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: parser.add_argument( "--android-adaptive-icon-background", dest="android_adaptive_icon_background", - help="the color which will be used to fill out the background of the adaptive icon", + help="the color which will be used to fill out the background of the " + "adaptive icon", required=False, ) parser.add_argument( @@ -323,7 +334,8 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: parser.add_argument( "--splash-dark-color", dest="splash_dark_color", - help="background color in dark mode of app splash screen on iOS, Android and web", + help="background color in dark mode of app splash screen on iOS, Android " + "and web", required=False, ) parser.add_argument( @@ -365,14 +377,16 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: "--ios-provisioning-profile", dest="ios_provisioning_profile", type=str, - help="provisioning profile name or UUID that used to sign and export iOS app", + help="provisioning profile name or UUID that used to sign and " + "export iOS app", required=False, ) parser.add_argument( "--ios-signing-certificate", dest="ios_signing_certificate", type=str, - help="provide a certificate name, SHA-1 hash, or automatic selector to use for signing iOS app bundle", + help="provide a certificate name, SHA-1 hash, or automatic selector to use " + "for signing iOS app bundle", required=False, ) parser.add_argument( @@ -384,15 +398,9 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: parser.add_argument( "--web-renderer", dest="web_renderer", - choices=["canvaskit", "html"], + choices=["auto", "canvaskit", "skwasm"], help="renderer to use (web only)", ) - parser.add_argument( - "--use-color-emoji", - dest="use_color_emoji", - action="store_true", - help="enables color emojis with CanvasKit renderer (web only)", - ) parser.add_argument( "--route-url-strategy", dest="route_url_strategy", @@ -411,6 +419,20 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: help="default color for your web application's user interface", required=False, ) + parser.add_argument( + "--no-wasm", + dest="no_wasm", + action="store_true", + default=False, + help="disable WASM target for web build.", + ) + parser.add_argument( + "--no-cdn", + dest="no_cdn", + action="store_true", + default=False, + help="disable loading of CanvasKit, Pyodide and fonts from CDN.", + ) parser.add_argument( "--split-per-abi", dest="split_per_abi", @@ -479,7 +501,8 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: dest="info_plist", nargs="+", default=[], - help='the list of "=|True|False" pairs to add to Info.plist for macOS and iOS builds', + help='the list of "=|True|False" pairs to add to Info.plist ' + "for macOS and iOS builds", ) parser.add_argument( "--macos-entitlements", @@ -493,21 +516,24 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: dest="android_features", nargs="+", default=[], - help='the list of "=True|False" features to add to AndroidManifest.xml', + help='the list of "=True|False" features to add to ' + "AndroidManifest.xml", ) parser.add_argument( "--android-permissions", dest="android_permissions", nargs="+", default=[], - help='the list of "=True|False" permissions to add to AndroidManifest.xml', + help='the list of "=True|False" permissions to add to ' + "AndroidManifest.xml", ) parser.add_argument( "--android-meta-data", dest="android_meta_data", nargs="+", default=[], - help='the list of "=" app meta-data entries to add to AndroidManifest.xml', + help='the list of "=" app meta-data entries to add to ' + "AndroidManifest.xml", ) parser.add_argument( "--permissions", @@ -515,12 +541,14 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: nargs="+", default=[], choices=["location", "camera", "microphone", "photo_library"], - help="the list of cross-platform permissions for iOS, Android and macOS apps", + help="the list of cross-platform permissions for iOS, Android " + "and macOS apps", ) parser.add_argument( "--deep-linking-scheme", dest="deep_linking_scheme", - help='deep linking URL scheme to configure for iOS and Android builds, i.g. "https" or "myapp"', + help="deep linking URL scheme to configure for iOS and Android builds, " + 'i.g. "https" or "myapp"', ) parser.add_argument( "--deep-linking-host", @@ -557,7 +585,8 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: parser.add_argument( "--build-version", dest="build_version", - help='build version - a "x.y.z" string used as the version number shown to users', + help='build version - a "x.y.z" string used as the version number ' + "shown to users", ) parser.add_argument( "--module-name", @@ -568,7 +597,8 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: "--template", dest="template", type=str, - help="a directory containing Flutter bootstrap template, or a URL to a git repository template", + help="a directory containing Flutter bootstrap template, or a URL " + "to a git repository template", ) parser.add_argument( "--template-dir", @@ -580,7 +610,8 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: "--template-ref", dest="template_ref", type=str, - help="the branch, tag or commit ID to checkout after cloning the repository with Flutter bootstrap template", + help="the branch, tag or commit ID to checkout after cloning " + "the repository with Flutter bootstrap template", ) parser.add_argument( "--show-platform-matrix", @@ -628,10 +659,15 @@ def handle(self, options: argparse.Namespace) -> None: self.cleanup( 0, message=( - f"Successfully built your [cyan]{self.platforms[self.options.target_platform]['status_text']}[/cyan]! {self.emojis['success']} " - f"Find it in [cyan]{self.rel_out_dir}[/cyan] directory. {self.emojis['directory']}" + f"Successfully built your [cyan]" + f"{self.platforms[self.options.target_platform]['status_text']}" + f"[/cyan]! {self.emojis['success']} " + f"Find it in [cyan]{self.rel_out_dir}[/cyan] directory. " + f"{self.emojis['directory']}" + ( - f"\nRun [cyan]python -m http.server --directory {self.rel_out_dir}[/cyan] command to start dev web server with your app. " + f"\nRun [cyan]python -m http.server --directory " + f"{self.rel_out_dir}[/cyan] command to start dev web server " + "with your app. " if self.options.target_platform == "web" else "" ) @@ -663,7 +699,8 @@ def initialize_build(self): ): self.cleanup( 1, - f"Path to Flet app does not exist or is not a directory: {self.python_app_path}", + f"Path to Flet app does not exist or is not a directory: " + f"{self.python_app_path}", ) # get `flutter` and `dart` executables from PATH @@ -839,7 +876,9 @@ def validate_target_platform(self): ].style = "bold red1" console.log(self.platform_matrix_table) - message = f"You {can_build_message} build [cyan]{self.options.target_platform}[/] on [magenta]{self.current_platform}[/]." + message = f"You {can_build_message} build " + f"[cyan]{self.options.target_platform}[/] on " + f"[magenta]{self.current_platform}[/]." self.cleanup(1, message) def validate_entry_point(self): @@ -862,8 +901,9 @@ def validate_entry_point(self): if not self.package_app_path.joinpath(self.python_module_filename).exists(): self.cleanup( 1, - f"{self.python_module_filename} not found in the root of Flet app directory. " - f"Use --module-name option to specify an entry point for your Flet app.", + f"{self.python_module_filename} not found in the root of Flet " + "app directory. Use --module-name option to specify an entry point " + "for your Flet app.", ) def setup_template_data(self): @@ -966,7 +1006,7 @@ def setup_template_data(self): for p in self.options.macos_entitlements: i = p.find("=") if i > -1: - macos_entitlements[p[:i]] = True if p[i + 1 :] == "True" else False + macos_entitlements[p[:i]] = p[i + 1 :] == "True" else: self.cleanup(1, f"Invalid macOS entitlement option: {p}") @@ -979,7 +1019,7 @@ def setup_template_data(self): for p in self.options.android_permissions: i = p.find("=") if i > -1: - android_permissions[p[:i]] = True if p[i + 1 :] == "True" else False + android_permissions[p[:i]] = p[i + 1 :] == "True" else: self.cleanup(1, f"Invalid Android permission option: {p}") @@ -992,7 +1032,7 @@ def setup_template_data(self): for p in self.options.android_features: i = p.find("=") if i > -1: - android_features[p[:i]] = True if p[i + 1 :] == "True" else False + android_features[p[:i]] = p[i + 1 :] == "True" else: self.cleanup(1, f"Invalid Android feature option: {p}") @@ -1078,7 +1118,9 @@ def setup_template_data(self): if self.options.target_platform in ["ipa"] and not ios_provisioning_profile: console.print( Panel( - "This build will generate an .xcarchive (Xcode Archive). To produce an .ipa (iOS App Package), please specify a Provisioning Profile.", + "This build will generate an .xcarchive (Xcode Archive). " + "To produce an .ipa (iOS App Package), please specify " + "a Provisioning Profile.", style=warning_style, ) ) @@ -1096,13 +1138,7 @@ def setup_template_data(self): "web_renderer": ( self.options.web_renderer or self.get_pyproject("tool.flet.web.renderer") - or "canvaskit" - ), - "use_color_emoji": ( - "true" - if self.options.use_color_emoji - or self.get_pyproject("tool.flet.web.use_color_emoji") - else "false" + or "auto" ), "pwa_background_color": ( self.options.pwa_background_color @@ -1112,6 +1148,13 @@ def setup_template_data(self): self.options.pwa_theme_color or self.get_pyproject("tool.flet.web.pwa_theme_color") ), + "no_wasm": ( + self.options.no_wasm + or self.get_pyproject("tool.flet.web.wasm") == False # noqa: E712 + ), + "no_cdn": ( + self.options.no_cdn or self.get_pyproject("tool.flet.web.cdn") == False # noqa: E712 + ), "base_url": f"/{base_url}/" if base_url else "/", "split_per_abi": split_per_abi, "project_name": project_name, @@ -1202,7 +1245,8 @@ def create_flutter_project(self, second_pass=False): hash_changed = hash.has_changed() if hash_changed: - # if options.clear_cache is set, delete any existing Flutter bootstrap project directory + # if options.clear_cache is set, delete any existing Flutter bootstrap + # project directory if ( self.options.clear_cache and self.flutter_dir.exists() @@ -1216,7 +1260,8 @@ def create_flutter_project(self, second_pass=False): if not second_pass: self.flutter_dir.mkdir(parents=True, exist_ok=True) self.update_status( - f'[bold blue]Creating Flutter bootstrap project from {template_url} with ref "{template_ref}"...' + "[bold blue]Creating Flutter bootstrap project from " + f'{template_url} with ref "{template_ref}"...' ) try: @@ -1249,7 +1294,8 @@ def create_flutter_project(self, second_pass=False): if not second_pass: console.log( - f'Created Flutter bootstrap project from {template_url} with ref "{template_ref}" {self.emojis["checkmark"]}' + f"Created Flutter bootstrap project from {template_url} " + f'with ref "{template_ref}" {self.emojis["checkmark"]}' ) hash.commit() @@ -1301,12 +1347,13 @@ def update_flutter_dependencies(self): pubspec["dependencies"][k] = v # make sure project_name is not named as any of the dependencies - for dep in pubspec["dependencies"].keys(): + for dep in pubspec["dependencies"]: if dep == self.template_data["project_name"]: self.cleanup( 1, - f"Project name cannot have the same name as one of its dependencies: {dep}. " - f"Use --project option to specify a different project name.", + f"Project name cannot have the same name as one of its " + f"dependencies: {dep}. Use --project option to specify " + "a different project name.", ) self.save_yaml(self.pubspec_path, pubspec) @@ -1762,10 +1809,16 @@ def package_python_app(self): for i in range(0, len(toml_dependencies)): package_name = Requirement(toml_dependencies[i]).name if package_name in dev_packages: - dev_path = Path(dev_packages[package_name]) + package_location = dev_packages[package_name] + dev_path = Path(package_location) if not dev_path.is_absolute(): dev_path = (self.python_app_path / dev_path).resolve() - toml_dependencies[i] = f"{package_name} @ file://{dev_path}" + if dev_path.exists(): + toml_dependencies[i] = f"{package_name} @ file://{dev_path}" + else: + toml_dependencies[i] = ( + f"{package_name} @ {package_location}" + ) dev_packages_configured = True if dev_packages_configured: toml_dependencies.append("--no-cache-dir") @@ -1937,7 +1990,8 @@ def flutter_build(self): assert self.template_data self.update_status( - f"[bold blue]Building [cyan]{self.platforms[self.options.target_platform]['status_text']}[/cyan]..." + f"[bold blue]Building [cyan]" + f"{self.platforms[self.options.target_platform]['status_text']}[/cyan]..." ) # flutter build build_args = [ @@ -1956,6 +2010,9 @@ def flutter_build(self): self.build_dir / "site-packages" ) + if self.package_platform == "Pyodide" and not self.template_data["no_wasm"]: + build_args.append("--wasm") + android_signing_key_store = ( self.options.android_signing_key_store or self.get_pyproject("tool.flet.android.signing.key_store") @@ -2043,7 +2100,8 @@ def flutter_build(self): console.log(build_result.stderr, style=error_style) self.cleanup(build_result.returncode if build_result.returncode else 1) console.log( - f"Built [cyan]{self.platforms[self.options.target_platform]['status_text']}[/cyan] {self.emojis['checkmark']}", + f"Built [cyan]{self.platforms[self.options.target_platform]['status_text']}" + f"[/cyan] {self.emojis['checkmark']}", ) def copy_build_output(self): @@ -2062,6 +2120,14 @@ def copy_build_output(self): elif arch in {"arm64", "aarch64"}: arch = "arm64" + def make_ignore_fn(out_dir, out_glob): + def ignore(path, names): + if path == out_dir and out_glob != "*": + return [f for f in os.listdir(path) if f != out_glob] + return [] + + return ignore + for build_output in self.platforms[self.options.target_platform]["outputs"]: build_output_dir = ( str(self.flutter_dir.joinpath(build_output)) @@ -2085,20 +2151,20 @@ def copy_build_output(self): shutil.rmtree(str(self.out_dir)) self.out_dir.mkdir(parents=True, exist_ok=True) - def ignore_build_output(path, files): - if path == build_output_dir and build_output_glob != "*": - return [f for f in os.listdir(path) if f != build_output_glob] - return [] - # copy build result to out_dir - copy_tree(build_output_dir, str(self.out_dir), ignore=ignore_build_output) + copy_tree( + build_output_dir, + str(self.out_dir), + ignore=make_ignore_fn(build_output_dir, build_output_glob), + ) if self.options.target_platform == "web" and self.assets_path.exists(): # copy `assets` directory contents to the output directory copy_tree(str(self.assets_path), str(self.out_dir)) console.log( - f"Copied build to [cyan]{self.rel_out_dir}[/cyan] directory {self.emojis['checkmark']}" + f"Copied build to [cyan]{self.rel_out_dir}[/cyan] " + f"directory {self.emojis['checkmark']}" ) def find_platform_image( @@ -2152,8 +2218,10 @@ def cleanup(self, exit_code: int, message: Optional[str] = None): else "Error building Flet app - see the log of failed command above." ) - # windows has been reported to raise encoding errors when running `flutter doctor` - # so skip running `flutter doctor` if no_rich_output is True and platform is Windows + # windows has been reported to raise encoding errors + # when running `flutter doctor` + # so skip running `flutter doctor` if no_rich_output is True + # and platform is Windows if not ( (self.no_rich_output and self.current_platform == "Windows") or self.skip_flutter_doctor diff --git a/sdk/python/packages/flet-cli/src/flet_cli/commands/publish.py b/sdk/python/packages/flet-cli/src/flet_cli/commands/publish.py index c609c08d9..f1155aa0d 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/commands/publish.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/commands/publish.py @@ -1,15 +1,13 @@ import argparse import os -import re import shutil import sys import tarfile import tempfile from pathlib import Path -from flet.core.types import WebRenderer +from flet.controls.types import WebRenderer from flet.utils import copy_tree, is_within_directory, random_string - from flet_cli.commands.base import BaseCommand from flet_cli.utils.project_dependencies import ( get_poetry_dependencies, @@ -82,17 +80,10 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: parser.add_argument( "--web-renderer", dest="web_renderer", - choices=["canvaskit", "html"], - default="canvaskit", + choices=["auto", "canvaskit", "skwasm"], + default="auto", help="web renderer to use", ) - parser.add_argument( - "--use-color-emoji", - dest="use_color_emoji", - action="store_true", - default=False, - help="enables color emojis with CanvasKit renderer", - ) parser.add_argument( "--route-url-strategy", dest="route_url_strategy", @@ -112,13 +103,25 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: help="default color for your web application's user interface", required=False, ) + parser.add_argument( + "--no-cdn", + dest="no_cdn", + action="store_true", + default=False, + help="disable loading of CanvasKit, Pyodide and fonts from CDN.", + ) def handle(self, options: argparse.Namespace) -> None: import flet.version from flet.utils.pip import ensure_flet_web_package_installed ensure_flet_web_package_installed() - from flet_web import get_package_web_dir, patch_index_html, patch_manifest_json + from flet_web import ( + get_package_web_dir, + patch_font_manifest_json, + patch_index_html, + patch_manifest_json, + ) # constants dist_name = "dist" @@ -190,7 +193,7 @@ def handle(self, options: argparse.Namespace) -> None: deps = toml_dependencies print(f"pyproject.toml dependencies: {deps}") elif requirements_txt.exists(): - with open(requirements_txt, "r", encoding="utf-8") as f: + with open(requirements_txt, encoding="utf-8") as f: deps = list( filter( lambda dep: not dep.startswith("#"), @@ -212,15 +215,16 @@ def handle(self, options: argparse.Namespace) -> None: def filter_tar(tarinfo: tarfile.TarInfo): full_path = os.path.join(script_dir, tarinfo.name) if ( - tarinfo.name.startswith(".") - or tarinfo.name.startswith("__pycache__") - or tarinfo.name == reqs_filename + ( + tarinfo.name.startswith(".") + or tarinfo.name.startswith("__pycache__") + or tarinfo.name == reqs_filename + ) + or assets_dir + and is_within_directory(assets_dir, full_path) + or is_within_directory(dist_dir, full_path) ): return None - elif assets_dir and is_within_directory(assets_dir, full_path): - return None - elif is_within_directory(dist_dir, full_path): - return None # tarinfo.uid = tarinfo.gid = 0 # tarinfo.uname = tarinfo.gname = "root" if tarinfo.name != "": @@ -272,6 +276,8 @@ def filter_tar(tarinfo: tarfile.TarInfo): "tool.flet.web.pwa_theme_color" ) + no_cdn = options.no_cdn or get_pyproject("tool.flet.web.cdn") == False # noqa: E712 + print("Patching index.html") patch_index_html( index_path=os.path.join(dist_dir, "index.html"), @@ -282,25 +288,16 @@ def filter_tar(tarinfo: tarfile.TarInfo): pyodide_pre=options.pre, pyodide_script_path=str(script_path), web_renderer=WebRenderer( - ( - options.web_renderer - or get_pyproject("tool.flet.web.renderer") - or "canvaskit" - ) - ), - use_color_emoji=( - True - if ( - options.use_color_emoji - or get_pyproject("tool.flet.web.use_color_emoji") - ) - else False + options.web_renderer + or get_pyproject("tool.flet.web.renderer") + or "auto" ), route_url_strategy=str( options.route_url_strategy or get_pyproject("tool.flet.web.route_url_strategy") or "path" ), + no_cdn=no_cdn, ) print("Patching manifest.json") @@ -312,3 +309,9 @@ def filter_tar(tarinfo: tarfile.TarInfo): background_color=pwa_background_color, theme_color=pwa_theme_color, ) + + if no_cdn: + print("Patching FontManifest.json") + patch_font_manifest_json( + manifest_path=os.path.join(dist_dir, "assets", "FontManifest.json") + ) diff --git a/sdk/python/packages/flet-cli/src/flet_cli/commands/run.py b/sdk/python/packages/flet-cli/src/flet_cli/commands/run.py index f2da83ab5..fd837a027 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/commands/run.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/commands/run.py @@ -19,11 +19,10 @@ open_in_browser, random_string, ) -from watchdog.events import FileSystemEventHandler -from watchdog.observers import Observer - from flet_cli.commands.base import BaseCommand from flet_cli.utils.pyproject_toml import load_pyproject_toml +from watchdog.events import FileSystemEventHandler +from watchdog.observers import Observer class Command(BaseCommand): @@ -128,7 +127,8 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: dest="ignore_dirs", type=str, default=None, - help="directories to ignore during watch. If more than one, separate with a comma.", + help="directories to ignore during watch. If more than one, separate" + "with a comma.", ) def handle(self, options: argparse.Namespace) -> None: @@ -328,7 +328,6 @@ def on_any_event(self, event): if ( self.watch_directory or event.src_path == self.script_path ) and event.event_type in ["modified", "deleted", "created", "moved"]: - current_time = time.time() if (current_time - self.last_time) > 0.5 and self.is_running: self.last_time = current_time diff --git a/sdk/python/packages/flet-cli/src/flet_cli/utils/project_dependencies.py b/sdk/python/packages/flet-cli/src/flet_cli/utils/project_dependencies.py index f39561bfc..308be1e4d 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/utils/project_dependencies.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/utils/project_dependencies.py @@ -2,8 +2,6 @@ # Based on: https://pypi.org/project/toml-to-requirements/ -from __future__ import annotations - from typing import Any, Optional diff --git a/sdk/python/packages/flet-desktop/pyproject.toml b/sdk/python/packages/flet-desktop/pyproject.toml index c1561a4ac..d1c31dc03 100644 --- a/sdk/python/packages/flet-desktop/pyproject.toml +++ b/sdk/python/packages/flet-desktop/pyproject.toml @@ -5,7 +5,7 @@ description = "Flet Desktop client in Flutter" authors = [{ name = "Appveyor Systems Inc.", email = "hello@flet.dev" }] license = "Apache-2.0" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "flet" ] diff --git a/sdk/python/packages/flet-desktop/src/flet_desktop/__init__.py b/sdk/python/packages/flet-desktop/src/flet_desktop/__init__.py index 1575638ee..6ec17da3b 100644 --- a/sdk/python/packages/flet-desktop/src/flet_desktop/__init__.py +++ b/sdk/python/packages/flet-desktop/src/flet_desktop/__init__.py @@ -10,8 +10,6 @@ import zipfile from pathlib import Path -import flet_desktop -import flet_desktop.version from flet.utils import ( get_arch, is_linux, @@ -21,6 +19,9 @@ safe_tar_extractall, ) +import flet_desktop +import flet_desktop.version + logger = logging.getLogger(flet_desktop.__name__) @@ -145,10 +146,12 @@ def __locate_and_unpack_flet_view(page_url, assets_dir, hidden): for f in os.listdir(temp_flet_dir): if f.endswith(".app"): app_name = f - assert ( - app_name is not None - ), f"Application bundle not found in {temp_flet_dir}" + assert app_name is not None, ( + f"Application bundle not found in {temp_flet_dir}" + ) app_path = temp_flet_dir.joinpath(app_name) + logger.info(f"page_url: {page_url}") + logger.info(f"pid_file: {pid_file}") args = ["open", str(app_path), "-n", "-W", "--args", page_url, pid_file] elif is_linux(): app_path = None diff --git a/sdk/python/packages/flet-web/pyproject.toml b/sdk/python/packages/flet-web/pyproject.toml index 4454573b6..2c4744c3d 100644 --- a/sdk/python/packages/flet-web/pyproject.toml +++ b/sdk/python/packages/flet-web/pyproject.toml @@ -5,7 +5,7 @@ description = "Flet web client in Flutter." authors = [{ name = "Appveyor Systems Inc.", email = "hello@flet.dev" }] license = "Apache-2.0" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "flet", "fastapi >=0.115.12", diff --git a/sdk/python/packages/flet-web/src/flet_web/__init__.py b/sdk/python/packages/flet-web/src/flet_web/__init__.py index a47b1b876..3beb3023a 100644 --- a/sdk/python/packages/flet-web/src/flet_web/__init__.py +++ b/sdk/python/packages/flet-web/src/flet_web/__init__.py @@ -1,9 +1,16 @@ import os from pathlib import Path -from flet_web.patch_index import patch_index_html, patch_manifest_json +from flet_web.patch_index import ( + patch_font_manifest_json, + patch_index_html, + patch_manifest_json, +) def get_package_web_dir(): web_root_dir = os.environ.get("FLET_WEB_PATH") return web_root_dir or str(Path(__file__).parent.joinpath("web")) + + +__all__ = ["patch_font_manifest_json", "patch_index_html", "patch_manifest_json"] diff --git a/sdk/python/packages/flet-web/src/flet_web/fastapi/__init__.py b/sdk/python/packages/flet-web/src/flet_web/fastapi/__init__.py index 8ad6e239a..0c5a40c8f 100644 --- a/sdk/python/packages/flet-web/src/flet_web/fastapi/__init__.py +++ b/sdk/python/packages/flet-web/src/flet_web/fastapi/__init__.py @@ -4,3 +4,5 @@ from flet_web.fastapi.flet_fastapi import FastAPI from flet_web.fastapi.flet_static_files import FletStaticFiles from flet_web.fastapi.flet_upload import FletUpload + +__all__ = ["app", "FletApp", "app_manager", "FastAPI", "FletStaticFiles", "FletUpload"] diff --git a/sdk/python/packages/flet-web/src/flet_web/fastapi/app.py b/sdk/python/packages/flet-web/src/flet_web/fastapi/app.py index 6cade3f3b..2bab51314 100644 --- a/sdk/python/packages/flet-web/src/flet_web/fastapi/app.py +++ b/sdk/python/packages/flet-web/src/flet_web/fastapi/app.py @@ -1,15 +1,18 @@ import asyncio import os -from typing import Awaitable, Callable, Optional, Union +from collections.abc import Awaitable +from typing import Callable, Optional, Union from fastapi import Request, WebSocket -from flet.core.page import Page -from flet.core.types import WebRenderer +from flet.controls.page import Page +from flet.controls.types import RouteUrlStrategy, WebRenderer +from starlette.middleware.base import BaseHTTPMiddleware from flet_web.fastapi.flet_app import ( DEFAULT_FLET_OAUTH_STATE_TIMEOUT, DEFAULT_FLET_SESSION_TIMEOUT, FletApp, + app_manager, ) from flet_web.fastapi.flet_fastapi import FastAPI from flet_web.fastapi.flet_oauth import FletOAuth @@ -18,15 +21,16 @@ def app( - session_handler: Union[Callable[[Page], Awaitable], Callable[[Page], None]], + main: Union[Callable[[Page], Awaitable], Callable[[Page], None]], + before_main: Union[Callable[[Page], Awaitable], Callable[[Page], None]], proxy_path: Optional[str] = None, assets_dir: Optional[str] = None, app_name: Optional[str] = None, app_short_name: Optional[str] = None, app_description: Optional[str] = None, - web_renderer: WebRenderer = WebRenderer.CANVAS_KIT, - use_color_emoji: bool = False, - route_url_strategy: str = "path", + web_renderer: WebRenderer = WebRenderer.AUTO, + route_url_strategy: RouteUrlStrategy = RouteUrlStrategy.PATH, + no_cdn: bool = False, upload_dir: Optional[str] = None, upload_endpoint_path: Optional[str] = None, max_upload_size: Optional[int] = None, @@ -38,20 +42,29 @@ def app( Mount all Flet FastAPI handlers in one call. Parameters: - * `session_handler` (function or coroutine) - application entry point - a method called for newly connected user. Handler must have 1 parameter: `page` - `Page` instance. + * `main` (function or coroutine) - application entry point - a method + called for newly connected user. Handler must have 1 parameter: `page` - `Page` + instance. + * `before_main` - a function that is called after Page was created, but before + calling `main`. * `assets_dir` (str, optional) - an absolute path to app's assets directory. * `app_name` (str, optional) - PWA application name. * `app_short_name` (str, optional) - PWA application short name. * `app_description` (str, optional) - PWA application description. - * `web_renderer` (WebRenderer) - web renderer defaulting to `WebRenderer.CANVAS_KIT`. - * `use_color_emoji` (bool) - whether to load a font with color emoji. Default is `False`. + * `web_renderer` (WebRenderer) - web renderer defaulting to `WebRenderer.AUTO`. * `route_url_strategy` (str) - routing URL strategy: `path` (default) or `hash`. + * `no_cdn` (bool) - do not load resources from CDN. * `upload_dir` (str) - an absolute path to a directory with uploaded files. - * `upload_endpoint_path` (str, optional) - absolute URL of upload endpoint, e.g. `/upload`. - * `max_upload_size` (str, int) - maximum size of a single upload, bytes. Unlimited if `None`. + * `upload_endpoint_path` (str, optional) - absolute URL of upload endpoint, + e.g. `/upload`. + * `max_upload_size` (str, int) - maximum size of a single upload, bytes. + Unlimited if `None`. * `secret_key` (str, optional) - secret key to sign and verify upload requests. - * `session_timeout_seconds` (int, optional)- session lifetime, in seconds, after user disconnected. - * `oauth_state_timeout_seconds` (int, optional) - OAuth state lifetime, in seconds, which is a maximum allowed time between starting OAuth flow and redirecting to OAuth callback URL. + * `session_timeout_seconds` (int, optional)- session lifetime, in seconds, after + user disconnected. + * `oauth_state_timeout_seconds` (int, optional) - OAuth state lifetime, in seconds, + which is a maximum allowed time between starting OAuth flow and redirecting + to OAuth callback URL. """ env_upload_dir = os.getenv("FLET_UPLOAD_DIR") @@ -80,8 +93,10 @@ def app( @fastapi_app.websocket(f"/{websocket_endpoint}") async def app_handler(websocket: WebSocket): await FletApp( - asyncio.get_running_loop(), - session_handler, + loop=asyncio.get_running_loop(), + executor=app_manager.executor, + main=main, + before_main=before_main, session_timeout_seconds=session_timeout_seconds, oauth_state_timeout_seconds=oauth_state_timeout_seconds, upload_endpoint_path=upload_endpoint_path, @@ -113,9 +128,21 @@ async def oauth_redirect_handler(request: Request): app_short_name=app_short_name, app_description=app_description, web_renderer=web_renderer, - use_color_emoji=use_color_emoji, route_url_strategy=route_url_strategy, + websocket_endpoint_path=websocket_endpoint, + no_cdn=no_cdn, ), ) + # Add middleware for custom headers + class CustomHeadersMiddleware(BaseHTTPMiddleware): + async def dispatch(self, request: Request, call_next): + response = await call_next(request) + response.headers["Cross-Origin-Opener-Policy"] = "same-origin" + response.headers["Cross-Origin-Embedder-Policy"] = "require-corp" + response.headers["Access-Control-Allow-Origin"] = "*" + return response + + fastapi_app.add_middleware(CustomHeadersMiddleware) + return fastapi_app 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 75828974e..0756b19db 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 @@ -1,42 +1,52 @@ import asyncio -import copy -import json +import inspect import logging import os import traceback +import weakref +from concurrent.futures import ThreadPoolExecutor from datetime import datetime, timedelta, timezone -from typing import Any, Dict, List, Optional +from typing import Any, Optional -import flet_web.fastapi as flet_fastapi +import msgpack from fastapi import WebSocket, WebSocketDisconnect -from flet.core.event import Event -from flet.core.local_connection import LocalConnection -from flet.core.page import Page, PageDisconnectedException -from flet.core.protocol import ( - ClientActions, +from flet.controls.base_control import BaseControl +from flet.controls.page import PageDisconnectedException, _session_page +from flet.controls.update_behavior import UpdateBehavior +from flet.messaging.connection import Connection +from flet.messaging.protocol import ( + ClientAction, ClientMessage, - Command, - CommandEncoder, - PageCommandResponsePayload, - PageCommandsBatchResponsePayload, - RegisterWebClientRequestPayload, + ControlEventBody, + InvokeMethodResponseBody, + RegisterClientRequestBody, + RegisterClientResponseBody, + UpdateControlPropsBody, + configure_encode_object_for_msgpack, + decode_ext_from_msgpack, ) +from flet.messaging.session import Session from flet.utils import random_string, sha1 + +import flet_web.fastapi as flet_fastapi from flet_web.fastapi.flet_app_manager import app_manager from flet_web.fastapi.oauth_state import OAuthState from flet_web.uploads import build_upload_url logger = logging.getLogger(flet_fastapi.__name__) +transport_log = logging.getLogger("flet_transport") DEFAULT_FLET_SESSION_TIMEOUT = 3600 DEFAULT_FLET_OAUTH_STATE_TIMEOUT = 600 -class FletApp(LocalConnection): +class FletApp(Connection): def __init__( self, loop: asyncio.AbstractEventLoop, - session_handler, + executor: ThreadPoolExecutor, + main, + before_main, session_timeout_seconds: int = DEFAULT_FLET_SESSION_TIMEOUT, oauth_state_timeout_seconds: int = DEFAULT_FLET_OAUTH_STATE_TIMEOUT, upload_endpoint_path: Optional[str] = None, @@ -47,21 +57,30 @@ def __init__( Parameters: - * `session_handler` (Coroutine) - application entry point - an async method called for newly connected user. Handler coroutine must have 1 parameter: `page` - `Page` instance. - * `session_timeout_seconds` (int, optional) - session lifetime, in seconds, after user disconnected. - * `oauth_state_timeout_seconds` (int, optional) - OAuth state lifetime, in seconds, which is a maximum allowed time between starting OAuth flow and redirecting to OAuth callback URL. - * `upload_endpoint_path` (str, optional) - absolute URL of upload endpoint, e.g. `/upload`. + * `session_handler` (Coroutine) - application entry point - an async method + called for newly connected user. Handler coroutine must have + 1 parameter: `page` - `Page` instance. + * `session_timeout_seconds` (int, optional) - session lifetime, in seconds, + after user disconnected. + * `oauth_state_timeout_seconds` (int, optional) - OAuth state lifetime, + in seconds, which is a maximum allowed time between starting OAuth flow + and redirecting to OAuth callback URL. + * `upload_endpoint_path` (str, optional) - absolute URL of upload endpoint, + e.g. `/upload`. * `secret_key` (str, optional) - secret key to sign upload requests. """ super().__init__() self.__id = random_string(8) logger.info(f"New FletApp: {self.__id}") - self.__page = None - self.__loop = loop - self.__session_handler = session_handler + self.__session = None + self.loop = loop + self.executor = executor + self.__main = main + self.__before_main = before_main self.__session_timeout_seconds = session_timeout_seconds self.__oauth_state_timeout_seconds = oauth_state_timeout_seconds + self.__running_tasks = set() env_session_timeout_seconds = os.getenv("FLET_SESSION_TIMEOUT") if env_session_timeout_seconds: @@ -74,6 +93,11 @@ def __init__( self.__upload_endpoint_path = upload_endpoint_path self.__secret_key = secret_key + app_id = self.__id + weakref.finalize( + self, lambda: logger.debug(f"FletApp was garbage collected: {app_id}") + ) + async def handle(self, websocket: WebSocket): """ Handle WebSocket connection. @@ -87,15 +111,9 @@ async def handle(self, websocket: WebSocket): self.client_ip = ( self.__websocket.client.host if self.__websocket.client else "" ).split(":")[0] - self.client_user_agent = ( - self.__websocket.headers["user-agent"] - if "user-agent" in self.__websocket.headers - else "" - ) + self.client_user_agent = self.__websocket.headers.get("user-agent", "") - self.pubsubhub = app_manager.get_pubsubhub( - self.__session_handler, loop=self.__loop - ) + self.pubsubhub = app_manager.get_pubsubhub(self.__main, loop=self.loop) self.page_url = str(websocket.url).rsplit("/", 1)[0] self.page_name = websocket.url.path.rsplit("/", 1)[0].lstrip("/") @@ -106,320 +124,209 @@ async def handle(self, websocket: WebSocket): await self.__websocket.accept() self.__send_queue = asyncio.Queue() - st = asyncio.create_task(self.__send_loop()) + send_loop_task = asyncio.create_task(self.__send_loop()) await self.__receive_loop() - st.cancel() + await send_loop_task - async def __on_event(self, e): - session = await app_manager.get_session( - self.__get_unique_session_id(e.sessionID) + # disconnect this connection from a session + await app_manager.disconnect_session( + self.__get_unique_session_id(self.__session.id), + self.__session_timeout_seconds, ) - if session is not None: - try: - await session.on_event_async( - Event(e.eventTarget, e.eventName, e.eventData) - ) - except PageDisconnectedException: - logger.debug( - f"Event handler attempted to update disconnected page: {e.sessionID}" - ) - if e.eventTarget == "page" and e.eventName == "close": - logger.info(f"Session closed: {e.sessionID}") - await app_manager.delete_session( - self.__get_unique_session_id(e.sessionID) - ) - async def __on_session_created(self, session_data): - logger.info(f"Start session: {session_data.sessionID}") - session_id = session_data.sessionID + async def __on_session_created(self): + assert self.__session + logger.info(f"Start session: {self.__session.id}") try: - assert self.__session_handler is not None - if asyncio.iscoroutinefunction(self.__session_handler): - await self.__session_handler(self.__page) + assert self.__main is not None + _session_page.set(self.__session.page) + UpdateBehavior.reset() + + if asyncio.iscoroutinefunction(self.__main): + await self.__main(self.__session.page) + + elif inspect.isasyncgenfunction(self.__main): + async for _ in self.__main(self.__session.page): + if UpdateBehavior.auto_update_enabled(): + await self.__session.auto_update(self.__session.page) + + elif inspect.isgeneratorfunction(self.__main): + for _ in self.__main(self.__session.page): + if UpdateBehavior.auto_update_enabled(): + await self.__session.auto_update(self.__session.page) else: - # run in thread pool - await asyncio.get_running_loop().run_in_executor( - app_manager.executor, self.__session_handler, self.__page - ) + self.__main(self.__session.page) + + if UpdateBehavior.auto_update_enabled(): + await self.__session.auto_update(self.__session.page) except PageDisconnectedException: logger.debug( - f"Session handler attempted to update disconnected page: {session_id}" + "Session handler attempted to update disconnected page: " + f"{self.__session.id}" ) except BrokenPipeError: - logger.info(f"Session handler terminated: {session_id}") + logger.info(f"Session handler terminated: {self.__session.id}") except Exception as e: print( - f"Unhandled error processing page session {session_id}:", + f"Unhandled error processing page session {self.__session.id}:", traceback.format_exc(), ) - assert self.__page - self.__page.error(f"There was an error while processing your request: {e}") + assert self.__session + self.__session.error( + f"There was an error while processing your request: {e}" + ) async def __send_loop(self): assert self.__websocket assert self.__send_queue while True: message = await self.__send_queue.get() + if message is None: + break + try: - await self.__websocket.send_text(message) + await self.__websocket.send_bytes(message) except Exception: # re-enqueue the message to repeat it when re-connected - self.__send_queue.put_nowait(message) + # self.__send_queue.put_nowait(message) raise + self.__websocket = None + self.__send_queue = None async def __receive_loop(self): assert self.__websocket try: while True: - await self.__on_message(await self.__websocket.receive_text()) + data = await self.__websocket.receive_bytes() + await self.__on_message( + msgpack.unpackb(data, ext_hook=decode_ext_from_msgpack) + ) except Exception as e: if not isinstance(e, WebSocketDisconnect): - logger.warning(f"Receive loop error: {e}") - if self.__page: - await app_manager.disconnect_session( - self.__get_unique_session_id(self.__page.session_id), - self.__session_timeout_seconds, + logger.warning(f"Receive loop error: {e}", exc_info=True) + if self.__session: + # terminate __send_loop + await self.__send_queue.put(None) + + async def __on_message(self, data: Any): + action = ClientAction(data[0]) + body = data[1] + transport_log.debug(f"_on_message: {action} {body}") + task = None + if action == ClientAction.REGISTER_CLIENT: + req = RegisterClientRequestBody(**body) + + new_session = False + + # try to retrieve existing session + if req.session_id: + self.__session = await app_manager.get_session( + self.__get_unique_session_id(req.session_id) ) - self.__websocket = None - self.__send_queue = None - async def __on_message(self, data: str): - logger.debug(f"_on_message: {data}") - msg_dict = json.loads(data) - msg = ClientMessage(**msg_dict) - if msg.action == ClientActions.REGISTER_WEB_CLIENT: - self._client_details = RegisterWebClientRequestPayload(**msg.payload) - - new_session = True - if ( - not self._client_details.sessionId - or await app_manager.get_session( - self.__get_unique_session_id(self._client_details.sessionId) - ) - is None - ): - # generate session ID - self._client_details.sessionId = random_string(16) - - # create new Page object - self.__page = Page( - self, - self._client_details.sessionId, - executor=app_manager.executor, - loop=asyncio.get_running_loop(), - ) + # re-create session + if self.__session is None: + new_session = True + + # create new session + self.__session = Session(self) # register session await app_manager.add_session( - self.__get_unique_session_id(self._client_details.sessionId), - self.__page, - ) - else: - # existing session - logger.info( - f"Existing session requested: {self._client_details.sessionId}" + self.__get_unique_session_id(self.__session.id), + self.__session, ) - self.__page = await app_manager.get_session( - self.__get_unique_session_id(self._client_details.sessionId) + + original_route = self.__session.page.route + + # apply page patch + self.__session.apply_page_patch(req.page) + + if new_session: + if asyncio.iscoroutinefunction(self.__before_main): + await self.__before_main(self.__session.page) + elif callable(self.__before_main): + self.__before_main(self.__session.page) + + # register response + self.send_message( + ClientMessage( + ClientAction.REGISTER_CLIENT, + RegisterClientResponseBody( + session_id=self.__session.id, + page_patch=self.__session.get_page_patch(), + error="", + ), ) - new_session = False - - # update page props - assert self.__page - original_route = self.__page.route - self.__page._set_attr("route", self._client_details.pageRoute, False) - self.__page._set_attr("pwa", self._client_details.isPWA, False) - self.__page._set_attr("web", self._client_details.isWeb, False) - self.__page._set_attr("debug", self._client_details.isDebug, False) - self.__page._set_attr("platform", self._client_details.platform, False) - self.__page._set_attr( - "platformBrightness", self._client_details.platformBrightness, False - ) - self.__page._set_attr("media", self._client_details.media, False) - self.__page._set_attr("width", self._client_details.pageWidth, False) - self.__page._set_attr("height", self._client_details.pageHeight, False) - self.__page._set_attr( - "windowWidth", self._client_details.windowWidth, False - ) - self.__page._set_attr( - "windowHeight", self._client_details.windowHeight, False - ) - self.__page._set_attr("windowTop", self._client_details.windowTop, False) - self.__page._set_attr("windowLeft", self._client_details.windowLeft, False) - self.__page._set_attr("clientIP", self.client_ip, False) - self.__page._set_attr("clientUserAgent", self.client_user_agent, False) - - p = self.__page.snapshot.get("page") - if not p: - p = { - "i": "page", - "t": "page", - "p": "", - "c": [], - } - self.__page.snapshot["page"] = p - self.__page.copy_attrs(p) - - # send register response - self.__send( - self._create_register_web_client_response(controls=self.__page.snapshot) ) # start session if new_session: - asyncio.create_task( - self.__on_session_created(self._create_session_handler_arg()) - ) + asyncio.create_task(self.__on_session_created()) else: await app_manager.reconnect_session( - self.__get_unique_session_id(self._client_details.sessionId), self + self.__get_unique_session_id(self.__session.id), self ) - if original_route != self.__page.route: - self.__page.go(self.__page.route) + if ( + self.__session.page.route + and self.__session.page.route != original_route + ): + self.__session.page.go(self.__session.page.route) - elif msg.action == ClientActions.PAGE_EVENT_FROM_WEB: - if self.__on_event is not None: - asyncio.create_task( - self.__on_event(self._create_page_event_handler_arg(msg)) - ) + elif action == ClientAction.CONTROL_EVENT: + req = ControlEventBody(**body) + task = asyncio.create_task( + self.__session.dispatch_event(req.target, req.name, req.data) + ) + + elif action == ClientAction.UPDATE_CONTROL_PROPS: + req = UpdateControlPropsBody(**body) + self.__session.apply_patch(req.id, req.props) + + elif action == ClientAction.INVOKE_METHOD: + req = InvokeMethodResponseBody(**body) + self.__session.handle_invoke_method_results( + req.control_id, req.call_id, req.result, req.error + ) - elif msg.action == ClientActions.UPDATE_CONTROL_PROPS: - if self.__on_event is not None: - asyncio.create_task( - self.__on_event(self._create_update_control_props_handler_arg(msg)) - ) else: # it's something else - raise Exception(f'Unknown message "{msg.action}": {msg.payload}') - - def _process_get_upload_url_command(self, attrs): - assert len(attrs) == 2, '"getUploadUrl" command has wrong number of attrs' - assert ( - self.__upload_endpoint_path - ), "upload_path should be specified to enable uploads" - return ( - build_upload_url( - self.__upload_endpoint_path, - attrs["file"], - int(attrs["expires"]), - self.__secret_key, - ), - None, + raise Exception(f'Unknown message "{action}": {body}') + + if task: + self.__running_tasks.add(task) + task.add_done_callback(self.__running_tasks.discard) + + def send_message(self, message: ClientMessage): + transport_log.debug(f"send_message: {message}") + m = msgpack.packb( + [message.action, message.body], + default=configure_encode_object_for_msgpack(BaseControl), + ) + self.__send_queue.put_nowait(m) + + def get_upload_url(self, file_name: str, expires: int) -> str: + assert self.__upload_endpoint_path, ( + "upload_path should be specified to enable uploads" + ) + return build_upload_url( + self.__upload_endpoint_path, + file_name, + expires, + self.__secret_key, ) - def __process_oauth_authorize_command(self, attrs: Dict[str, Any]): + def oauth_authorize(self, attrs: dict[str, Any]): state_id = attrs["state"] state = OAuthState( - session_id=self.__get_unique_session_id(self._client_details.sessionId), + session_id=self.__get_unique_session_id(self.__session.id), expires_at=datetime.now(timezone.utc) + timedelta(seconds=self.__oauth_state_timeout_seconds), - complete_page_html=attrs.get("completePageHtml", None), - complete_page_url=attrs.get("completePageUrl", None), + complete_page_html=attrs.get("completePageHtml"), + complete_page_url=attrs.get("completePageUrl"), ) app_manager.store_state(state_id, state) - return ( - "", - None, - ) - - def _process_add_command(self, command: Command): - assert self.__page - result, message = super()._process_add_command(command) - if message: - for oc in message.payload.controls: - control = copy.deepcopy(oc) - id = control["i"] - pid = control["p"] - parent = self.__page.snapshot[pid] - assert parent, f"parent control not found: {pid}" - if id not in parent["c"]: - if "at" in control: - parent["c"].insert(int(control["at"]), id) - else: - parent["c"].append(id) - self.__page.snapshot[id] = control - return result, message - - def _process_set_command(self, values, attrs): - assert self.__page - result, message = super()._process_set_command(values, attrs) - control = self.__page.snapshot.get(values[0]) - if control: - for k, v in attrs.items(): - control[k] = v - return result, message - - def _process_remove_command(self, values): - assert self.__page - result, message = super()._process_remove_command(values) - for id in values: - control = self.__page.snapshot.get(id) - assert ( - control is not None - ), f"_process_remove_command: control with ID '{id}' not found." - for cid in self.__get_all_descendant_ids(id): - self.__page.snapshot.pop(cid, None) - # delete control itself - self.__page.snapshot.pop(id, None) - # remove id from parent - parent = self.__page.snapshot.get(control["p"]) - if parent: - parent["c"].remove(id) - return result, message - - def _process_clean_command(self, values): - assert self.__page - result, message = super()._process_clean_command(values) - for id in values: - for cid in self.__get_all_descendant_ids(id): - self.__page.snapshot.pop(cid, None) - return result, message - - def __get_all_descendant_ids(self, id): - assert self.__page - ids = [] - control = self.__page.snapshot.get(id) - if control: - for cid in control["c"]: - ids.append(cid) - ids.extend(self.__get_all_descendant_ids(cid)) - return ids - - def send_command(self, session_id: str, command: Command): - if command.name == "oauthAuthorize": - result, message = self.__process_oauth_authorize_command(command.attrs) - else: - result, message = self._process_command(command) - if message: - self.__send(message) - return PageCommandResponsePayload(result=result, error="") - - def send_commands(self, session_id: str, commands: List[Command]): - results = [] - messages = [] - for command in commands: - if command.name == "oauthAuthorize": - result, message = self.__process_oauth_authorize_command(command.attrs) - else: - result, message = self._process_command(command) - if command.name in ["add", "get"]: - results.append(result) - if message: - messages.append(message) - if len(messages) > 0: - self.__send(ClientMessage(ClientActions.PAGE_CONTROLS_BATCH, messages)) - return PageCommandsBatchResponsePayload(results=results, error="") - - def __send(self, message: ClientMessage): - m = json.dumps(message, cls=CommandEncoder, separators=(",", ":")) - logger.debug(f"__send: {m}") - if self.__send_queue: - self.__loop.call_soon_threadsafe(self.__send_queue.put_nowait, m) - - def _get_next_control_id(self): - assert self.__page - return self.__page.get_next_control_id() def __get_unique_session_id(self, session_id: str): client_hash = sha1(f"{self.client_ip}{self.client_user_agent}") @@ -427,4 +334,4 @@ def __get_unique_session_id(self, session_id: str): def dispose(self): logger.info(f"Disposing FletApp: {self.__id}") - self.__page = None + self.__session = None diff --git a/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_app_manager.py b/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_app_manager.py index f08b6bf6f..c5db30029 100644 --- a/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_app_manager.py +++ b/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_app_manager.py @@ -1,17 +1,14 @@ import asyncio import logging import shutil -import threading import traceback from concurrent.futures import ThreadPoolExecutor from datetime import datetime, timezone from typing import Optional -from flet.core.connection import Connection -from flet.core.locks import NopeLock -from flet.core.page import Page -from flet.core.pubsub.pubsub_hub import PubSubHub -from flet.utils import is_pyodide +from flet.messaging.connection import Connection +from flet.messaging.session import Session +from flet.pubsub.pubsub_hub import PubSubHub import flet_web.fastapi as flet_fastapi from flet_web.fastapi.oauth_state import OAuthState @@ -25,15 +22,12 @@ class FletAppManager: """ def __init__(self): - self.__sessions_lock = asyncio.Lock() - self.__sessions: dict[str, Page] = {} + self.__sessions: dict[str, Session] = {} self.__evict_sessions_task = None self.__states: dict[str, OAuthState] = {} - self.__states_lock = threading.Lock() if not is_pyodide() else NopeLock() self.__evict_oauth_states_task = None self.__temp_dirs = {} self.__executor = ThreadPoolExecutor(thread_name_prefix="flet_fastapi") - self.__pubsubhubs_lock = threading.Lock() if not is_pyodide() else NopeLock() self.__pubsubhubs = {} @property @@ -43,19 +37,19 @@ def executor(self): def get_pubsubhub( self, session_handler, loop: Optional[asyncio.AbstractEventLoop] = None ): - with self.__pubsubhubs_lock: - psh = self.__pubsubhubs.get(session_handler, None) - if psh is None: - psh = PubSubHub( - loop=loop or asyncio.get_running_loop(), - executor=self.__executor, - ) - self.__pubsubhubs[session_handler] = psh - return psh + psh = self.__pubsubhubs.get(session_handler, None) + if psh is None: + psh = PubSubHub( + loop=loop or asyncio.get_running_loop(), + executor=self.__executor, + ) + self.__pubsubhubs[session_handler] = psh + return psh async def start(self): """ - Background task evicting expired app data. Must be called at FastAPI application startup. + Background task evicting expired app data. Must be called at FastAPI + application startup. """ if not self.__evict_sessions_task: logger.info("Starting up Flet App Manager") @@ -77,44 +71,31 @@ async def shutdown(self): if self.__evict_oauth_states_task: self.__evict_oauth_states_task.cancel() - async def get_session(self, session_id: str) -> Optional[Page]: - async with self.__sessions_lock: - return self.__sessions.get(session_id) + async def get_session(self, session_id: str) -> Optional[Session]: + return self.__sessions.get(session_id) - async def add_session(self, session_id: str, conn: Page): - async with self.__sessions_lock: - self.__sessions[session_id] = conn - logger.info( - f"New session created ({len(self.__sessions)} total): {session_id}" - ) + async def add_session(self, session_id: str, session: Session): + self.__sessions[session_id] = session + logger.info(f"New session created ({len(self.__sessions)} total): {session_id}") async def reconnect_session(self, session_id: str, conn: Connection): logger.info(f"Session reconnected: {session_id}") - async with self.__sessions_lock: - if session_id in self.__sessions: - page = self.__sessions[session_id] - old_conn = page.connection - await page._connect(conn) - if old_conn: - old_conn.dispose() + if session_id in self.__sessions: + session = self.__sessions[session_id] + await session.connect(conn) async def disconnect_session(self, session_id: str, session_timeout_seconds: int): logger.info(f"Session disconnected: {session_id}") - async with self.__sessions_lock: - if session_id in self.__sessions: - await self.__sessions[session_id]._disconnect(session_timeout_seconds) + if session_id in self.__sessions: + await self.__sessions[session_id].disconnect(session_timeout_seconds) async def delete_session(self, session_id: str): - async with self.__sessions_lock: - page = self.__sessions.pop(session_id, None) - total = len(self.__sessions) - if page is not None: + session = self.__sessions.pop(session_id, None) + total = len(self.__sessions) + if session is not None: logger.info(f"Delete session ({total} left): {session_id}") try: - old_conn = page.connection - page._close() - if old_conn: - old_conn.dispose() + session.close() except Exception as e: logger.error( f"Error deleting expired session: {e} {traceback.format_exc()}" @@ -122,12 +103,10 @@ async def delete_session(self, session_id: str): def store_state(self, state_id: str, state: OAuthState): logger.info(f"Store oauth state: {state_id}") - with self.__states_lock: - self.__states[state_id] = state + self.__states[state_id] = state def retrieve_state(self, state_id: str) -> Optional[OAuthState]: - with self.__states_lock: - return self.__states.pop(state_id, None) + return self.__states.pop(state_id, None) def add_temp_dir(self, temp_dir: str): self.__temp_dirs[temp_dir] = True @@ -136,30 +115,28 @@ async def __evict_expired_sessions(self): while True: await asyncio.sleep(10) session_ids = [] - async with self.__sessions_lock: - for session_id, page in self.__sessions.items(): - if page.expires_at and datetime.now(timezone.utc) > page.expires_at: - session_ids.append(session_id) + for session_id, session in self.__sessions.items(): + if ( + session.expires_at + and datetime.now(timezone.utc) > session.expires_at + ): + session_ids.append(session_id) for session_id in session_ids: await self.delete_session(session_id) async def __evict_expired_oauth_states(self): while True: await asyncio.sleep(10) - with self.__states_lock: - ids = [] - for id, state in self.__states.items(): - if ( - state.expires_at - and datetime.now(timezone.utc) > state.expires_at - ): - ids.append(id) + ids = [] + for id, state in self.__states.items(): + if state.expires_at and datetime.now(timezone.utc) > state.expires_at: + ids.append(id) for id in ids: logger.info(f"Delete expired oauth state: {id}") self.retrieve_state(id) def delete_temp_dirs(self): - for temp_dir in self.__temp_dirs.keys(): + for temp_dir in self.__temp_dirs: logger.info(f"Deleting temp dir: {temp_dir}") shutil.rmtree(temp_dir, ignore_errors=True) diff --git a/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_oauth.py b/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_oauth.py index 2a255b6d5..def75fc21 100644 --- a/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_oauth.py +++ b/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_oauth.py @@ -1,5 +1,6 @@ from fastapi import HTTPException, Request from fastapi.responses import HTMLResponse, RedirectResponse + from flet_web.fastapi.flet_app_manager import app_manager @@ -33,7 +34,7 @@ async def handle(self, request: Request): if not session: raise HTTPException(status_code=500, detail="Session not found") - await session._authorize_callback_async( + await session.page._authorize_callback_async( { "state": state_id, "code": request.query_params.get("code"), diff --git a/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_static_files.py b/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_static_files.py index 60e447fec..a7584a603 100644 --- a/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_static_files.py +++ b/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_static_files.py @@ -3,16 +3,22 @@ import shutil import tempfile from pathlib import Path -from typing import Optional, Tuple +from typing import Optional -import flet_web.fastapi as flet_fastapi from fastapi.staticfiles import StaticFiles -from flet.core.types import WebRenderer +from flet.controls.types import RouteUrlStrategy, WebRenderer from flet.utils import Once, get_bool_env_var -from flet_web import get_package_web_dir, patch_index_html, patch_manifest_json -from flet_web.fastapi.flet_app_manager import app_manager from starlette.types import Receive, Scope, Send +import flet_web.fastapi as flet_fastapi +from flet_web import ( + get_package_web_dir, + patch_font_manifest_json, + patch_index_html, + patch_manifest_json, +) +from flet_web.fastapi.flet_app_manager import app_manager + logger = logging.getLogger(flet_fastapi.__name__) @@ -27,10 +33,11 @@ class FletStaticFiles(StaticFiles): * `app_name` (str, optional) - PWA application name. * `app_short_name` (str, optional) - PWA application short name. * `app_description` (str, optional) - PWA application description. - * `web_renderer` (WebRenderer) - web renderer defaulting to `WebRenderer.CANVAS_KIT`. - * `use_color_emoji` (bool) - whether to load a font with color emoji. Default is `False`. + * `web_renderer` (WebRenderer) - web renderer defaulting to `WebRenderer.AUTO`. * `route_url_strategy` (str) - routing URL strategy: `path` (default) or `hash`. - * `websocket_endpoint_path` (str, optional) - absolute URL of Flet app WebSocket handler. Default is `{app_mount_path}/ws`. + * `no_cdn` - do not load CanvasKit, Pyodide and fonts from CDN + * `websocket_endpoint_path` (str, optional) - absolute URL of Flet app + WebSocket handler. Default is `{app_mount_path}/ws`. """ def __init__( @@ -40,21 +47,22 @@ def __init__( app_name: Optional[str] = None, app_short_name: Optional[str] = None, app_description: Optional[str] = None, - web_renderer: WebRenderer = WebRenderer.CANVAS_KIT, - use_color_emoji: bool = False, - route_url_strategy: str = "path", + web_renderer: WebRenderer = WebRenderer.AUTO, + route_url_strategy: RouteUrlStrategy = RouteUrlStrategy.PATH, + no_cdn: bool = False, websocket_endpoint_path: Optional[str] = None, ) -> None: - self.index = "index.html" - self.manifest_json = "manifest.json" + self.index = ["index.html"] + self.manifest_json = ["manifest.json"] + self.font_manifest_json = ["assets", "FontManifest.json"] self.__proxy_path = proxy_path self.__assets_dir = assets_dir self.__app_name = app_name self.__app_short_name = app_short_name self.__app_description = app_description self.__web_renderer = web_renderer - self.__use_color_emoji = use_color_emoji self.__route_url_strategy = route_url_strategy + self.__no_cdn = no_cdn self.__websocket_endpoint_path = websocket_endpoint_path self.__once = Once() @@ -62,23 +70,23 @@ def __init__( if env_web_renderer: self.__web_renderer = WebRenderer(env_web_renderer) - env_use_color_emoji = get_bool_env_var("FLET_WEB_USE_COLOR_EMOJI") - if env_use_color_emoji is not None: - self.__use_color_emoji = env_use_color_emoji - env_route_url_strategy = os.getenv("FLET_WEB_ROUTE_URL_STRATEGY") if env_route_url_strategy: - self.__route_url_strategy = env_route_url_strategy + self.__route_url_strategy = RouteUrlStrategy(env_route_url_strategy) + + env_no_cdn = get_bool_env_var("FLET_WEB_NO_CDN") + if env_no_cdn is not None: + self.__no_cdn = env_no_cdn logger.info(f"Web renderer configured: {self.__web_renderer}") - logger.info(f"Use color emoji: {self.__use_color_emoji}") logger.info(f"Route URL strategy configured: {self.__route_url_strategy}") + logger.info(f"No CDN configured: {self.__no_cdn}") async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: await self.__once.do(self.__config, scope["root_path"]) await super().__call__(scope, receive, send) - def lookup_path(self, path: str) -> Tuple[str, Optional[os.stat_result]]: + def lookup_path(self, path: str) -> tuple[str, Optional[os.stat_result]]: """Returns the index file when no match is found. Args: @@ -92,7 +100,7 @@ def lookup_path(self, path: str) -> Tuple[str, Optional[os.stat_result]]: # if a file cannot be found if stat_result is None: - return super().lookup_path(self.index) + return super().lookup_path(self.index[0]) return full_path, stat_result @@ -125,33 +133,22 @@ async def __config(self, root_path: str): logger.info(f"Assets dir: {self.__assets_dir}") + def copy_temp_web_file(paths: list[str]): + if self.__assets_dir and os.path.exists( + p := os.path.join(self.__assets_dir, *paths) + ): + src_path = p + else: + src_path = os.path.join(web_dir, *paths) + dst_path = os.path.join(temp_dir, *paths) + Path(dst_path).parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(src_path, dst_path) + # copy index.html from assets_dir or web_dir - if self.__assets_dir and os.path.exists( - os.path.join(self.__assets_dir, self.index) - ): - shutil.copyfile( - os.path.join(self.__assets_dir, self.index), - os.path.join(temp_dir, self.index), - ) - else: - shutil.copyfile( - os.path.join(web_dir, self.index), - os.path.join(temp_dir, self.index), - ) + copy_temp_web_file(self.index) # copy manifest.json from assets_dir or web_dir - if self.__assets_dir and os.path.exists( - os.path.join(self.__assets_dir, self.manifest_json) - ): - shutil.copyfile( - os.path.join(self.__assets_dir, self.manifest_json), - os.path.join(temp_dir, self.manifest_json), - ) - else: - shutil.copyfile( - os.path.join(web_dir, self.manifest_json), - os.path.join(temp_dir, self.manifest_json), - ) + copy_temp_web_file(self.manifest_json) ws_path = self.__websocket_endpoint_path if not ws_path: @@ -160,23 +157,30 @@ async def __config(self, root_path: str): # replace variables in index.html and manifest.json patch_index_html( - index_path=os.path.join(temp_dir, self.index), + index_path=os.path.join(temp_dir, *self.index), base_href=self.__app_mount_path, websocket_endpoint_path=ws_path, app_name=self.__app_name, app_description=self.__app_description, - web_renderer=WebRenderer(self.__web_renderer), - use_color_emoji=self.__use_color_emoji, + web_renderer=self.__web_renderer, route_url_strategy=self.__route_url_strategy, + no_cdn=self.__no_cdn, ) patch_manifest_json( - manifest_path=os.path.join(temp_dir, self.manifest_json), + manifest_path=os.path.join(temp_dir, *self.manifest_json), app_name=self.__app_name, app_short_name=self.__app_short_name, app_description=self.__app_description, ) + if self.__no_cdn: + # copy FontManifest.json from assets_dir or web_dir + copy_temp_web_file(self.font_manifest_json) + patch_font_manifest_json( + manifest_path=os.path.join(temp_dir, *self.font_manifest_json) + ) + # set html=True to resolve the index even when no # the base path is passed in super().__init__(directory=temp_dir, packages=None, html=True, check_dir=True) diff --git a/sdk/python/packages/flet-web/src/flet_web/fastapi/serve_fastapi_web_app.py b/sdk/python/packages/flet-web/src/flet_web/fastapi/serve_fastapi_web_app.py index 616e644f3..6eeadf718 100644 --- a/sdk/python/packages/flet-web/src/flet_web/fastapi/serve_fastapi_web_app.py +++ b/sdk/python/packages/flet-web/src/flet_web/fastapi/serve_fastapi_web_app.py @@ -1,9 +1,9 @@ import asyncio import logging -from typing import Optional +from typing import Any, Optional, Union import uvicorn -from flet.core.types import WebRenderer +from flet.controls.types import RouteUrlStrategy, WebRenderer import flet_web.fastapi import flet_web.fastapi as flet_fastapi @@ -22,25 +22,27 @@ async def close(self): def get_fastapi_web_app( - session_handler, + main, + before_main, page_name: str, - assets_dir, - upload_dir, - web_renderer: Optional[WebRenderer], - use_color_emoji, - route_url_strategy, + assets_dir: str, + upload_dir: str, + web_renderer: WebRenderer = WebRenderer.AUTO, + route_url_strategy: RouteUrlStrategy = RouteUrlStrategy.PATH, + no_cdn: bool = False, ): web_path = f"/{page_name.strip('/')}" app = flet_web.fastapi.FastAPI() app.mount( web_path, flet_web.fastapi.app( - session_handler, + main, + before_main=before_main, upload_dir=upload_dir, assets_dir=assets_dir, - web_renderer=web_renderer if web_renderer else WebRenderer.AUTO, - use_color_emoji=use_color_emoji, + web_renderer=web_renderer, route_url_strategy=route_url_strategy, + no_cdn=no_cdn, ), ) @@ -48,21 +50,21 @@ def get_fastapi_web_app( async def serve_fastapi_web_app( - session_handler, - host, - url_host, - port, + main, + before_main, + host: str, + url_host: str, + port: int, page_name: str, - assets_dir, - upload_dir, - web_renderer: Optional[WebRenderer], - use_color_emoji, - route_url_strategy, - blocking, - on_startup, - log_level, + assets_dir: str, + upload_dir: str, + web_renderer: WebRenderer = WebRenderer.AUTO, + route_url_strategy: RouteUrlStrategy = RouteUrlStrategy.PATH, + no_cdn: bool = False, + blocking: bool = False, + on_startup: Optional[Any] = None, + log_level: Optional[Union[str, int]] = None, ): - web_path = f"/{page_name.strip('/')}" page_url = f"http://{url_host}:{port}{web_path if web_path != '/' else ''}" @@ -75,12 +77,13 @@ def startup(): app.mount( web_path, flet_web.fastapi.app( - session_handler, + main, + before_main=before_main, upload_dir=upload_dir, assets_dir=assets_dir, - web_renderer=web_renderer if web_renderer else WebRenderer.AUTO, - use_color_emoji=use_color_emoji, + web_renderer=web_renderer, route_url_strategy=route_url_strategy, + no_cdn=no_cdn, ), ) config = uvicorn.Config(app, host=host, port=port, log_level=log_level) diff --git a/sdk/python/packages/flet-web/src/flet_web/patch_index.py b/sdk/python/packages/flet-web/src/flet_web/patch_index.py index 7bc1fdeba..51fd68272 100644 --- a/sdk/python/packages/flet-web/src/flet_web/patch_index.py +++ b/sdk/python/packages/flet-web/src/flet_web/patch_index.py @@ -3,7 +3,7 @@ from pathlib import Path from typing import Optional -from flet.core.types import WebRenderer +from flet.controls.types import RouteUrlStrategy, WebRenderer def patch_index_html( @@ -16,64 +16,54 @@ def patch_index_html( pyodide_pre: bool = False, pyodide_script_path: str = "", web_renderer: WebRenderer = WebRenderer.AUTO, - use_color_emoji: bool = False, - route_url_strategy: str = "path", + route_url_strategy: RouteUrlStrategy = RouteUrlStrategy.PATH, + no_cdn: bool = False, ): - with open(index_path, "r", encoding="utf-8") as f: + with open(index_path, encoding="utf-8") as f: index = f.read() + app_config = [] + if pyodide and pyodide_script_path: module_name = Path(pyodide_script_path).stem - pyodideCode = f""" - - - """ - index = index.replace("", pyodideCode) - index = index.replace("%FLET_WEB_PYODIDE%", str(pyodide).lower()) - index = index.replace( - "", - f'', - ) + app_config.append("flet.pyodide = true;") + app_config.append(f"flet.micropipIncludePre = {str(pyodide_pre).lower()};") + app_config.append(f'flet.pythonModuleName = "{module_name}";') + + app_config.append(f"flet.noCdn={str(no_cdn).lower()};") + app_config.append(f'flet.webRenderer="{web_renderer.value}";') + app_config.append(f'flet.routeUrlStrategy="{route_url_strategy.value}";') + + if websocket_endpoint_path: + app_config.append(f'flet.webSocketEndpoint="{websocket_endpoint_path}";') + index = index.replace( - "", - f"", + "", + "".format("\n".join(app_config)), ) - index = index.replace("%FLET_ROUTE_URL_STRATEGY%", route_url_strategy) if base_href: base_url = base_href.strip("/").strip() index = index.replace( '', - ''.format( - "/" if base_url == "" else "/{}/".format(base_url) - ), - ) - if websocket_endpoint_path: - index = re.sub( - r"\", - r''.format( - websocket_endpoint_path - ), - index, + ''.format("/" if base_url == "" else f"/{base_url}/"), ) + if app_name: index = re.sub( r"\", - r''.format(app_name), + rf'', index, ) index = re.sub( r"\(.+)", - r"{}".format(app_name), + rf"{app_name}", index, ) if app_description: index = re.sub( r"\", - r''.format(app_description), + rf'', index, ) @@ -89,7 +79,7 @@ def patch_manifest_json( background_color: Optional[str] = None, theme_color: Optional[str] = None, ): - with open(manifest_path, "r", encoding="utf-8") as f: + with open(manifest_path, encoding="utf-8") as f: manifest = json.loads(f.read()) if app_name: @@ -110,3 +100,13 @@ def patch_manifest_json( with open(manifest_path, "w", encoding="utf-8") as f: f.write(json.dumps(manifest, indent=2)) + + +def patch_font_manifest_json(manifest_path: str): + with open(manifest_path, encoding="utf-8") as f: + manifest = json.loads(f.read()) + + manifest.append({"family": "Roboto", "fonts": [{"asset": "fonts/roboto.woff2"}]}) + + with open(manifest_path, "w", encoding="utf-8") as f: + f.write(json.dumps(manifest, indent=2)) diff --git a/sdk/python/packages/flet/pyproject.toml b/sdk/python/packages/flet/pyproject.toml index fe2ed6718..8afd9d3cb 100644 --- a/sdk/python/packages/flet/pyproject.toml +++ b/sdk/python/packages/flet/pyproject.toml @@ -5,7 +5,7 @@ description = "Flet for Python - easily build interactive multi-platform apps in authors = [{name = "Appveyor Systems Inc.", email = "hello@flet.dev"}] license = "Apache-2.0" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "flet-cli; extra == 'cli'", "flet-desktop; extra == 'desktop' and (platform_system == 'Darwin' or platform_system == 'Windows')", diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index 45ec85679..c0575635f 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -1,319 +1,401 @@ -from flet.app import app, app_async -from flet.core import ( - alignment, - border, - border_radius, - dropdown, - dropdownm2, - margin, - padding, - painting, - size, -) -from flet.core.adaptive_control import AdaptiveControl -from flet.core.alert_dialog import AlertDialog -from flet.core.alignment import Alignment, Axis -from flet.core.animated_switcher import AnimatedSwitcher, AnimatedSwitcherTransition -from flet.core.animation import Animation, AnimationCurve, AnimationStyle -from flet.core.app_bar import AppBar -from flet.core.audio import ( - Audio, - AudioDurationChangeEvent, - AudioPositionChangeEvent, - AudioState, - AudioStateChangeEvent, -) -from flet.core.audio_recorder import ( - AudioEncoder, - AudioRecorder, - AudioRecorderState, - AudioRecorderStateChangeEvent, -) -from flet.core.auto_complete import ( - AutoComplete, - AutoCompleteSelectEvent, - AutoCompleteSuggestion, -) -from flet.core.autofill_group import ( - AutofillGroup, - AutofillGroupDisposeAction, - AutofillHint, +from flet.app import app, app_async, run, run_async +from flet.controls import alignment, border, border_radius, margin, padding +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.alignment import Alignment, Axis, OptionalAlignment, OptionalAxis +from flet.controls.animation import ( + Animation, + AnimationCurve, + AnimationStyle, + AnimationValue, + OptionalAnimation, + OptionalAnimationCurve, + OptionalAnimationStyle, + OptionalAnimationValue, +) +from flet.controls.base_control import BaseControl, control +from flet.controls.blur import ( + Blur, + BlurTileMode, + BlurValue, + OptionalBlurTileMode, + OptionalBlurValue, +) +from flet.controls.border import ( + Border, + BorderSide, + BorderSideStrokeAlign, + BorderSideStrokeAlignValue, + BorderStyle, + OptionalBorder, + OptionalBorderSide, + OptionalBorderSideStrokeAlign, + OptionalBorderSideStrokeAlignValue, +) +from flet.controls.border_radius import ( + BorderRadius, + BorderRadiusValue, + OptionalBorderRadiusValue, ) -from flet.core.badge import Badge -from flet.core.banner import Banner -from flet.core.blur import Blur, BlurTileMode -from flet.core.border import Border, BorderSide, BorderSideStrokeAlign -from flet.core.border_radius import BorderRadius -from flet.core.bottom_app_bar import BottomAppBar -from flet.core.bottom_sheet import BottomSheet -from flet.core.box import ( +from flet.controls.box import ( BoxConstraints, BoxDecoration, + BoxFit, BoxShadow, BoxShape, ColorFilter, DecorationImage, FilterQuality, + OptionalBoxConstraints, + OptionalBoxDecoration, + OptionalBoxFit, + OptionalBoxShadow, + OptionalBoxShape, + OptionalColorFilter, + OptionalDecorationImage, + OptionalFilterQuality, + OptionalShadowBlurStyle, + OptionalShadowValue, ShadowBlurStyle, + ShadowValue, ) -from flet.core.button import Button -from flet.core.buttons import ( +from flet.controls.buttons import ( BeveledRectangleBorder, ButtonStyle, CircleBorder, ContinuousRectangleBorder, + OptionalButtonStyle, + OptionalOutlinedBorder, OutlinedBorder, RoundedRectangleBorder, StadiumBorder, ) -from flet.core.card import Card, CardVariant -from flet.core.charts.bar_chart import BarChart, BarChartEvent -from flet.core.charts.bar_chart_group import BarChartGroup -from flet.core.charts.bar_chart_rod import BarChartRod -from flet.core.charts.bar_chart_rod_stack_item import BarChartRodStackItem -from flet.core.charts.chart_axis import ChartAxis -from flet.core.charts.chart_axis_label import ChartAxisLabel -from flet.core.charts.chart_grid_lines import ChartGridLines -from flet.core.charts.chart_point_line import ChartPointLine -from flet.core.charts.chart_point_shape import ( - ChartCirclePoint, - ChartCrossPoint, - ChartPointShape, - ChartSquarePoint, -) -from flet.core.charts.line_chart import LineChart, LineChartEvent, LineChartEventSpot -from flet.core.charts.line_chart_data import LineChartData -from flet.core.charts.line_chart_data_point import LineChartDataPoint -from flet.core.charts.pie_chart import PieChart, PieChartEvent -from flet.core.charts.pie_chart_section import PieChartSection -from flet.core.checkbox import Checkbox -from flet.core.chip import Chip -from flet.core.circle_avatar import CircleAvatar -from flet.core.colors import Colors -from flet.core.column import Column -from flet.core.container import Container, ContainerTapEvent -from flet.core.control import Control -from flet.core.control_event import ControlEvent -from flet.core.cupertino_action_sheet import CupertinoActionSheet -from flet.core.cupertino_action_sheet_action import CupertinoActionSheetAction -from flet.core.cupertino_activity_indicator import CupertinoActivityIndicator -from flet.core.cupertino_alert_dialog import CupertinoAlertDialog -from flet.core.cupertino_app_bar import CupertinoAppBar -from flet.core.cupertino_bottom_sheet import CupertinoBottomSheet -from flet.core.cupertino_button import CupertinoButton -from flet.core.cupertino_checkbox import CupertinoCheckbox -from flet.core.cupertino_colors import CupertinoColors -from flet.core.cupertino_context_menu import CupertinoContextMenu -from flet.core.cupertino_context_menu_action import CupertinoContextMenuAction -from flet.core.cupertino_date_picker import ( - CupertinoDatePicker, - CupertinoDatePickerDateOrder, - CupertinoDatePickerMode, +from flet.controls.colors import Colors +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control, OptionalControl +from flet.controls.control_builder import ControlBuilder +from flet.controls.control_event import ( + ControlEvent, + ControlEventHandler, + Event, + EventControlType, + EventHandler, + OptionalControlEventHandler, + OptionalEventHandler, +) +from flet.controls.control_state import ( + ControlState, + ControlStateValue, ) -from flet.core.cupertino_dialog_action import CupertinoDialogAction -from flet.core.cupertino_filled_button import CupertinoFilledButton -from flet.core.cupertino_icons import CupertinoIcons -from flet.core.cupertino_list_tile import CupertinoListTile -from flet.core.cupertino_navigation_bar import CupertinoNavigationBar -from flet.core.cupertino_picker import CupertinoPicker -from flet.core.cupertino_radio import CupertinoRadio -from flet.core.cupertino_segmented_button import CupertinoSegmentedButton -from flet.core.cupertino_slider import CupertinoSlider -from flet.core.cupertino_sliding_segmented_button import CupertinoSlidingSegmentedButton -from flet.core.cupertino_switch import CupertinoSwitch -from flet.core.cupertino_textfield import CupertinoTextField, VisibilityMode -from flet.core.cupertino_timer_picker import ( - CupertinoTimerPicker, - CupertinoTimerPickerMode, +from flet.controls.core.animated_switcher import ( + AnimatedSwitcher, + AnimatedSwitcherTransition, ) -from flet.core.datatable import ( - DataCell, - DataColumn, - DataColumnSortEvent, - DataRow, - DataTable, -) -from flet.core.date_picker import ( - DatePicker, - DatePickerEntryMode, - DatePickerEntryModeChangeEvent, - DatePickerMode, +from flet.controls.core.autofill_group import ( + AutofillGroup, + AutofillGroupDisposeAction, + AutofillHint, ) -from flet.core.dismissible import ( +from flet.controls.core.column import Column +from flet.controls.core.dismissible import ( Dismissible, DismissibleDismissEvent, DismissibleUpdateEvent, ) -from flet.core.divider import Divider -from flet.core.drag_target import DragTarget, DragTargetEvent -from flet.core.draggable import Draggable -from flet.core.dropdown import Dropdown, DropdownOption -from flet.core.dropdownm2 import DropdownM2 -from flet.core.elevated_button import ElevatedButton -from flet.core.exceptions import ( - FletException, - FletUnimplementedPlatformEception, - FletUnsupportedPlatformException, +from flet.controls.core.drag_target import ( + DragTarget, + DragTargetEvent, + DragTargetLeaveEvent, + DragWillAcceptEvent, +) +from flet.controls.core.draggable import Draggable +from flet.controls.core.flet_app import FletApp +from flet.controls.core.gesture_detector import GestureDetector +from flet.controls.core.grid_view import GridView +from flet.controls.core.icon import Icon +from flet.controls.core.image import Image +from flet.controls.core.interactive_viewer import InteractiveViewer +from flet.controls.core.list_view import ListView +from flet.controls.core.markdown import ( + Markdown, + MarkdownCodeTheme, + MarkdownCustomCodeTheme, + MarkdownExtensionSet, + MarkdownStyleSheet, ) -from flet.core.expansion_panel import ExpansionPanel, ExpansionPanelList -from flet.core.expansion_tile import ExpansionTile, TileAffinity -from flet.core.file_picker import ( - FilePicker, - FilePickerFileType, - FilePickerResultEvent, - FilePickerUploadEvent, - FilePickerUploadFile, +from flet.controls.core.pagelet import Pagelet +from flet.controls.core.placeholder import Placeholder +from flet.controls.core.reorderable_draggable import ReorderableDraggable +from flet.controls.core.responsive_row import ResponsiveRow +from flet.controls.core.row import Row +from flet.controls.core.safe_area import SafeArea +from flet.controls.core.semantics import Semantics +from flet.controls.core.shader_mask import ShaderMask +from flet.controls.core.stack import Stack, StackFit +from flet.controls.core.text import ( + Text, + TextAffinity, + TextSelection, + TextSelectionChangeCause, + TextSelectionChangeEvent, +) +from flet.controls.core.text_span import TextSpan +from flet.controls.core.transparent_pointer import TransparentPointer +from flet.controls.core.view import View +from flet.controls.core.window import ( + Window, + WindowEvent, + WindowEventType, + WindowResizeEdge, +) +from flet.controls.core.window_drag_area import WindowDragArea +from flet.controls.cupertino import cupertino_colors, cupertino_icons +from flet.controls.cupertino.cupertino_action_sheet import CupertinoActionSheet +from flet.controls.cupertino.cupertino_action_sheet_action import ( + CupertinoActionSheetAction, +) +from flet.controls.cupertino.cupertino_activity_indicator import ( + CupertinoActivityIndicator, +) +from flet.controls.cupertino.cupertino_alert_dialog import CupertinoAlertDialog +from flet.controls.cupertino.cupertino_app_bar import CupertinoAppBar +from flet.controls.cupertino.cupertino_bottom_sheet import CupertinoBottomSheet +from flet.controls.cupertino.cupertino_button import ( + CupertinoButton, + CupertinoButtonSize, +) +from flet.controls.cupertino.cupertino_checkbox import CupertinoCheckbox +from flet.controls.cupertino.cupertino_colors import CupertinoColors +from flet.controls.cupertino.cupertino_context_menu import CupertinoContextMenu +from flet.controls.cupertino.cupertino_context_menu_action import ( + CupertinoContextMenuAction, +) +from flet.controls.cupertino.cupertino_date_picker import ( + CupertinoDatePicker, + CupertinoDatePickerDateOrder, + CupertinoDatePickerMode, ) -from flet.core.filled_button import FilledButton -from flet.core.filled_tonal_button import FilledTonalButton -from flet.core.flashlight import Flashlight -from flet.core.flet_app import FletApp -from flet.core.floating_action_button import FloatingActionButton -from flet.core.form_field_control import InputBorder -from flet.core.geolocator import ( - Geolocator, - GeolocatorActivityType, - GeolocatorAndroidSettings, - GeolocatorAppleSettings, - GeolocatorPermissionStatus, - GeolocatorPosition, - GeolocatorPositionAccuracy, - GeolocatorPositionChangeEvent, - GeolocatorSettings, - GeolocatorWebSettings, -) -from flet.core.gesture_detector import ( +from flet.controls.cupertino.cupertino_dialog_action import CupertinoDialogAction +from flet.controls.cupertino.cupertino_filled_button import CupertinoFilledButton +from flet.controls.cupertino.cupertino_icons import CupertinoIcons +from flet.controls.cupertino.cupertino_list_tile import CupertinoListTile +from flet.controls.cupertino.cupertino_navigation_bar import CupertinoNavigationBar +from flet.controls.cupertino.cupertino_picker import CupertinoPicker +from flet.controls.cupertino.cupertino_radio import CupertinoRadio +from flet.controls.cupertino.cupertino_segmented_button import CupertinoSegmentedButton +from flet.controls.cupertino.cupertino_slider import CupertinoSlider +from flet.controls.cupertino.cupertino_sliding_segmented_button import ( + CupertinoSlidingSegmentedButton, +) +from flet.controls.cupertino.cupertino_switch import CupertinoSwitch +from flet.controls.cupertino.cupertino_textfield import ( + CupertinoTextField, + VisibilityMode, +) +from flet.controls.cupertino.cupertino_timer_picker import ( + CupertinoTimerPicker, + CupertinoTimerPickerMode, +) +from flet.controls.cupertino.cupertino_tinted_button import CupertinoTintedButton +from flet.controls.data_view import data_view +from flet.controls.dialog_control import DialogControl +from flet.controls.duration import ( + DateTimeValue, + Duration, + DurationValue, + OptionalDateTimeValue, + OptionalDuration, + OptionalDurationValue, +) +from flet.controls.events import ( DragEndEvent, DragStartEvent, DragUpdateEvent, - GestureDetector, HoverEvent, LongPressEndEvent, LongPressStartEvent, MultiTapEvent, + PointerEvent, ScaleEndEvent, ScaleStartEvent, ScaleUpdateEvent, ScrollEvent, TapEvent, ) -from flet.core.gradients import ( +from flet.controls.exceptions import ( + FletException, + FletUnimplementedPlatformEception, + FletUnsupportedPlatformException, +) +from flet.controls.geometry import Rect, Size +from flet.controls.gradients import ( + Gradient, GradientTileMode, LinearGradient, RadialGradient, SweepGradient, ) -from flet.core.grid_view import GridView -from flet.core.haptic_feedback import HapticFeedback -from flet.core.icon import Icon -from flet.core.icon_button import IconButton -from flet.core.icons import Icons -from flet.core.image import Image -from flet.core.interactive_viewer import ( - InteractiveViewer, - InteractiveViewerInteractionEndEvent, - InteractiveViewerInteractionStartEvent, - InteractiveViewerInteractionUpdateEvent, -) -from flet.core.list_tile import ListTile, ListTileStyle, ListTileTitleAlignment -from flet.core.list_view import ListView -from flet.core.lottie import Lottie -from flet.core.margin import Margin -from flet.core.markdown import ( - Markdown, - MarkdownCodeTheme, - MarkdownCustomCodeTheme, - MarkdownExtensionSet, - MarkdownStyleSheet, +from flet.controls.keys import ScrollKey, ValueKey +from flet.controls.margin import Margin, MarginValue, OptionalMarginValue +from flet.controls.material import dropdown, dropdownm2, icons +from flet.controls.material.alert_dialog import AlertDialog +from flet.controls.material.app_bar import AppBar +from flet.controls.material.auto_complete import ( + AutoComplete, + AutoCompleteSelectEvent, + AutoCompleteSuggestion, +) +from flet.controls.material.badge import Badge +from flet.controls.material.banner import Banner +from flet.controls.material.bottom_app_bar import BottomAppBar +from flet.controls.material.bottom_sheet import BottomSheet +from flet.controls.material.button import Button +from flet.controls.material.card import Card, CardVariant +from flet.controls.material.checkbox import Checkbox +from flet.controls.material.chip import Chip +from flet.controls.material.circle_avatar import CircleAvatar +from flet.controls.material.container import Container +from flet.controls.material.datatable import ( + DataCell, + DataColumn, + DataColumnSortEvent, + DataRow, + DataTable, +) +from flet.controls.material.date_picker import ( + DatePicker, + DatePickerEntryMode, + DatePickerEntryModeChangeEvent, + DatePickerMode, ) -from flet.core.menu_bar import MenuBar, MenuStyle -from flet.core.menu_item_button import MenuItemButton -from flet.core.navigation_bar import ( +from flet.controls.material.divider import Divider +from flet.controls.material.dropdown import Dropdown, DropdownOption +from flet.controls.material.dropdownm2 import DropdownM2 +from flet.controls.material.elevated_button import ElevatedButton +from flet.controls.material.expansion_panel import ExpansionPanel, ExpansionPanelList +from flet.controls.material.expansion_tile import ExpansionTile, TileAffinity +from flet.controls.material.filled_button import FilledButton +from flet.controls.material.filled_tonal_button import FilledTonalButton +from flet.controls.material.floating_action_button import FloatingActionButton +from flet.controls.material.form_field_control import InputBorder +from flet.controls.material.icon_button import IconButton +from flet.controls.material.icons import Icons +from flet.controls.material.list_tile import ( + ListTile, + ListTileStyle, + ListTileTitleAlignment, +) +from flet.controls.material.menu_bar import MenuBar, MenuStyle +from flet.controls.material.menu_item_button import MenuItemButton +from flet.controls.material.navigation_bar import ( NavigationBar, NavigationBarDestination, NavigationBarLabelBehavior, ) -from flet.core.navigation_drawer import ( +from flet.controls.material.navigation_drawer import ( NavigationDrawer, NavigationDrawerDestination, NavigationDrawerPosition, ) -from flet.core.navigation_rail import ( +from flet.controls.material.navigation_rail import ( NavigationRail, NavigationRailDestination, NavigationRailLabelType, ) -from flet.core.outlined_button import OutlinedButton -from flet.core.padding import Padding -from flet.core.page import ( +from flet.controls.material.outlined_button import OutlinedButton +from flet.controls.material.popup_menu_button import ( + PopupMenuButton, + PopupMenuItem, + PopupMenuPosition, +) +from flet.controls.material.progress_bar import ProgressBar +from flet.controls.material.progress_ring import ProgressRing +from flet.controls.material.radio import Radio +from flet.controls.material.radio_group import RadioGroup +from flet.controls.material.range_slider import RangeSlider +from flet.controls.material.reorderable_list_view import ( + OnReorderEvent, + ReorderableListView, +) +from flet.controls.material.search_bar import SearchBar +from flet.controls.material.segmented_button import Segment, SegmentedButton +from flet.controls.material.selection_area import SelectionArea +from flet.controls.material.slider import Slider, SliderInteraction +from flet.controls.material.snack_bar import ( + DismissDirection, + SnackBar, + SnackBarAction, + SnackBarBehavior, +) +from flet.controls.material.submenu_button import SubmenuButton +from flet.controls.material.switch import Switch +from flet.controls.material.tabs import Tab, Tabs +from flet.controls.material.text_button import TextButton +from flet.controls.material.textfield import ( + InputFilter, + KeyboardType, + NumbersOnlyInputFilter, + TextCapitalization, + TextField, + TextOnlyInputFilter, +) +from flet.controls.material.time_picker import ( + TimePicker, + TimePickerEntryMode, + TimePickerEntryModeChangeEvent, +) +from flet.controls.material.tooltip import Tooltip, TooltipTriggerMode +from flet.controls.material.vertical_divider import VerticalDivider +from flet.controls.multi_view import MultiView +from flet.controls.padding import OptionalPaddingValue, Padding, PaddingValue +from flet.controls.page import ( AppLifecycleStateChangeEvent, - BrowserContextMenu, KeyboardEvent, LoginEvent, + MultiViewAddEvent, + MultiViewRemoveEvent, Page, PageDisconnectedException, - PageMediaData, RouteChangeEvent, ViewPopEvent, - Window, - WindowEvent, - WindowResizeEvent, context, ) -from flet.core.pagelet import Pagelet -from flet.core.painting import ( +from flet.controls.page_view import PageMediaData, PageResizeEvent, PageView +from flet.controls.painting import ( Paint, PaintingStyle, PaintLinearGradient, PaintRadialGradient, PaintSweepGradient, ) -from flet.core.permission_handler import ( - PermissionHandler, - PermissionStatus, - PermissionType, +from flet.controls.query_string import QueryString +from flet.controls.ref import Ref +from flet.controls.scrollable_control import ( + OnScrollEvent, + ScrollableControl, + ScrollDirection, + ScrollType, ) -from flet.core.placeholder import Placeholder -from flet.core.popup_menu_button import ( - PopupMenuButton, - PopupMenuItem, - PopupMenuPosition, +from flet.controls.services.browser_context_menu import BrowserContextMenu +from flet.controls.services.file_picker import ( + FilePicker, + FilePickerFileType, + FilePickerUploadEvent, + FilePickerUploadFile, ) -from flet.core.progress_bar import ProgressBar -from flet.core.progress_ring import ProgressRing -from flet.core.pubsub.pubsub_client import PubSubClient -from flet.core.pubsub.pubsub_hub import PubSubHub -from flet.core.querystring import QueryString -from flet.core.radio import Radio -from flet.core.radio_group import RadioGroup -from flet.core.range_slider import RangeSlider -from flet.core.ref import Ref -from flet.core.reorderable_draggable import ReorderableDraggable -from flet.core.reorderable_list_view import OnReorderEvent, ReorderableListView -from flet.core.responsive_row import ResponsiveRow -from flet.core.rive import Rive -from flet.core.row import Row -from flet.core.safe_area import SafeArea -from flet.core.scrollable_control import OnScrollEvent -from flet.core.search_bar import SearchBar -from flet.core.segmented_button import Segment, SegmentedButton -from flet.core.selection_area import SelectionArea -from flet.core.semantics import Semantics -from flet.core.semantics_service import Assertiveness, SemanticsService -from flet.core.shader_mask import ShaderMask -from flet.core.shake_detector import ShakeDetector -from flet.core.size import Size -from flet.core.slider import Slider, SliderInteraction -from flet.core.snack_bar import DismissDirection, SnackBar, SnackBarBehavior -from flet.core.stack import Stack, StackFit -from flet.core.submenu_button import SubmenuButton -from flet.core.switch import Switch -from flet.core.tabs import Tab, Tabs -from flet.core.template_route import TemplateRoute -from flet.core.text import Text, TextAffinity, TextSelection -from flet.core.text_button import TextButton -from flet.core.text_span import TextSpan -from flet.core.text_style import ( +from flet.controls.services.haptic_feedback import HapticFeedback +from flet.controls.services.semantics_service import Assertiveness, SemanticsService +from flet.controls.services.service import Service +from flet.controls.services.shake_detector import ShakeDetector +from flet.controls.services.storage_paths import StoragePaths +from flet.controls.template_route import TemplateRoute +from flet.controls.text_style import ( + OptionalStrutStyle, + OptionalTextBaseline, + OptionalTextDecoration, + OptionalTextDecorationStyle, + OptionalTextOverflow, + OptionalTextStyle, + OptionalTextThemeStyle, TextBaseline, TextDecoration, TextDecorationStyle, @@ -321,21 +403,12 @@ TextStyle, TextThemeStyle, ) -from flet.core.textfield import ( - InputFilter, - KeyboardType, - NumbersOnlyInputFilter, - TextCapitalization, - TextField, - TextOnlyInputFilter, -) -from flet.core.theme import ( +from flet.controls.theme import ( AppBarTheme, BadgeTheme, BannerTheme, BottomAppBarTheme, BottomSheetTheme, - ButtonTheme, CardTheme, CheckboxTheme, ChipTheme, @@ -375,15 +448,18 @@ TimePickerTheme, TooltipTheme, ) -from flet.core.time_picker import ( - TimePicker, - TimePickerEntryMode, - TimePickerEntryModeChangeEvent, +from flet.controls.transform import ( + Offset, + OffsetValue, + OptionalOffsetValue, + OptionalRotateValue, + OptionalScaleValue, + Rotate, + RotateValue, + Scale, + ScaleValue, ) -from flet.core.tooltip import Tooltip, TooltipTriggerMode -from flet.core.transform import Offset, Rotate, Scale -from flet.core.transparent_pointer import TransparentPointer -from flet.core.types import ( +from flet.controls.types import ( FLET_APP, FLET_APP_HIDDEN, FLET_APP_WEB, @@ -391,19 +467,11 @@ AppLifecycleState, AppView, BlendMode, - BorderRadiusValue, Brightness, ClipBehavior, ColorEnums, ColorValue, - ControlEventType, - ControlState, - ControlStateValue, CrossAxisAlignment, - DateTimeValue, - Duration, - DurationValue, - EventType, FloatingActionButtonLocation, FontWeight, IconEnums, @@ -415,26 +483,25 @@ Locale, LocaleConfiguration, MainAxisAlignment, - MarginValue, MouseCursor, NotchShape, Number, - OffsetValue, - OnFocusEvent, - OptionalControlEventCallable, - OptionalEventCallable, + OptionalBool, + OptionalColorValue, + OptionalFloat, + OptionalInt, OptionalNumber, OptionalString, Orientation, - PaddingValue, PagePlatform, PointerDeviceType, ResponsiveNumber, - RotateValue, - ScaleValue, + ResponsiveRowBreakpoint, + RouteUrlStrategy, ScrollMode, StrokeCap, StrokeJoin, + StrOrControl, SupportsStr, TabAlignment, TextAlign, @@ -443,23 +510,466 @@ VerticalAlignment, VisualDensity, WebRenderer, - WindowEventType, ) -from flet.core.vertical_divider import VerticalDivider -from flet.core.video import ( - PlaylistMode, - Video, - VideoConfiguration, - VideoMedia, - VideoSubtitleConfiguration, -) -from flet.core.view import View -from flet.core.webview import ( - WebView, - WebviewConsoleMessageEvent, - WebviewJavaScriptEvent, - WebviewLogLevelSeverity, - WebviewRequestMethod, - WebviewScrollEvent, -) -from flet.core.window_drag_area import WindowDragArea +from flet.controls.update_behavior import UpdateBehavior +from flet.pubsub.pubsub_client import PubSubClient +from flet.pubsub.pubsub_hub import PubSubHub + +__all__ = [ + "app", + "app_async", + "run", + "run_async", + "alignment", + "border", + "border_radius", + "margin", + "padding", + "AdaptiveControl", + "Alignment", + "Axis", + "OptionalAlignment", + "OptionalAxis", + "Animation", + "AnimationCurve", + "AnimationStyle", + "AnimationValue", + "OptionalAnimation", + "OptionalAnimationCurve", + "OptionalAnimationStyle", + "OptionalAnimationValue", + "BaseControl", + "control", + "Blur", + "BlurTileMode", + "BlurValue", + "OptionalBlurTileMode", + "OptionalBlurValue", + "Border", + "BorderSide", + "BorderStyle", + "BorderSideStrokeAlign", + "BorderSideStrokeAlignValue", + "OptionalBorder", + "OptionalBorderSide", + "OptionalBorderSideStrokeAlign", + "OptionalBorderSideStrokeAlignValue", + "BorderRadius", + "BorderRadiusValue", + "OptionalBorderRadiusValue", + "BoxConstraints", + "BoxDecoration", + "BoxFit", + "BoxShadow", + "BoxShape", + "ColorFilter", + "DecorationImage", + "FilterQuality", + "OptionalBoxConstraints", + "OptionalBoxDecoration", + "OptionalBoxFit", + "OptionalBoxShadow", + "OptionalBoxShape", + "OptionalColorFilter", + "OptionalDecorationImage", + "OptionalFilterQuality", + "OptionalShadowBlurStyle", + "OptionalShadowValue", + "ShadowBlurStyle", + "ShadowValue", + "BeveledRectangleBorder", + "ButtonStyle", + "CircleBorder", + "ContinuousRectangleBorder", + "OptionalButtonStyle", + "OptionalOutlinedBorder", + "OutlinedBorder", + "RoundedRectangleBorder", + "StadiumBorder", + "Colors", + "ConstrainedControl", + "Control", + "OptionalControl", + "ControlEvent", + "ControlState", + "ControlStateValue", + "AnimatedSwitcher", + "AnimatedSwitcherTransition", + "AutofillGroup", + "AutofillGroupDisposeAction", + "AutofillHint", + "Column", + "ControlBuilder", + "Dismissible", + "DismissibleDismissEvent", + "DismissibleUpdateEvent", + "DragTarget", + "DragTargetEvent", + "DragTargetLeaveEvent", + "DragWillAcceptEvent", + "Draggable", + "FletApp", + "GestureDetector", + "GridView", + "Icon", + "Image", + "InteractiveViewer", + "ListView", + "Markdown", + "MarkdownCodeTheme", + "MarkdownCustomCodeTheme", + "MarkdownExtensionSet", + "MarkdownStyleSheet", + "Pagelet", + "Placeholder", + "ReorderableDraggable", + "ResponsiveRow", + "Row", + "SafeArea", + "Semantics", + "ShaderMask", + "Stack", + "StackFit", + "Text", + "TextAffinity", + "TextSelection", + "TextSpan", + "TransparentPointer", + "View", + "Window", + "WindowEvent", + "WindowEventType", + "WindowResizeEdge", + "WindowDragArea", + "cupertino_colors", + "cupertino_icons", + "CupertinoActionSheet", + "CupertinoActionSheetAction", + "CupertinoActivityIndicator", + "CupertinoAlertDialog", + "CupertinoAppBar", + "CupertinoBottomSheet", + "CupertinoButton", + "CupertinoButtonSize", + "CupertinoCheckbox", + "CupertinoColors", + "CupertinoContextMenu", + "CupertinoContextMenuAction", + "CupertinoDatePicker", + "CupertinoDatePickerDateOrder", + "CupertinoDatePickerMode", + "CupertinoDialogAction", + "CupertinoFilledButton", + "CupertinoIcons", + "CupertinoListTile", + "CupertinoNavigationBar", + "CupertinoPicker", + "CupertinoRadio", + "CupertinoSegmentedButton", + "CupertinoSlider", + "CupertinoSlidingSegmentedButton", + "CupertinoSwitch", + "CupertinoTextField", + "VisibilityMode", + "CupertinoTimerPicker", + "CupertinoTimerPickerMode", + "CupertinoTintedButton", + "data_view", + "DialogControl", + "DateTimeValue", + "Duration", + "DurationValue", + "OptionalDateTimeValue", + "OptionalDuration", + "OptionalDurationValue", + "DragEndEvent", + "DragStartEvent", + "DragUpdateEvent", + "HoverEvent", + "LongPressEndEvent", + "LongPressStartEvent", + "MultiTapEvent", + "PointerEvent", + "ScaleEndEvent", + "ScaleStartEvent", + "ScaleUpdateEvent", + "ScrollEvent", + "TapEvent", + "FletException", + "FletUnimplementedPlatformEception", + "FletUnsupportedPlatformException", + "Gradient", + "GradientTileMode", + "LinearGradient", + "RadialGradient", + "SweepGradient", + "Margin", + "MarginValue", + "OptionalMarginValue", + "dropdown", + "dropdownm2", + "icons", + "AlertDialog", + "AppBar", + "AutoComplete", + "AutoCompleteSelectEvent", + "AutoCompleteSuggestion", + "Badge", + "Banner", + "BottomAppBar", + "BottomSheet", + "Button", + "Card", + "CardVariant", + "Checkbox", + "Chip", + "CircleAvatar", + "Container", + "DataCell", + "DataColumn", + "DataColumnSortEvent", + "DataRow", + "DataTable", + "DatePicker", + "DatePickerEntryMode", + "DatePickerEntryModeChangeEvent", + "DatePickerMode", + "Divider", + "Dropdown", + "DropdownOption", + "DropdownM2", + "ElevatedButton", + "ExpansionPanel", + "ExpansionPanelList", + "ExpansionTile", + "TileAffinity", + "FilledButton", + "FilledTonalButton", + "FloatingActionButton", + "InputBorder", + "IconButton", + "Icons", + "ListTile", + "ListTileStyle", + "ListTileTitleAlignment", + "MenuBar", + "MenuStyle", + "MenuItemButton", + "NavigationBar", + "NavigationBarDestination", + "NavigationBarLabelBehavior", + "NavigationDrawer", + "NavigationDrawerDestination", + "NavigationDrawerPosition", + "NavigationRail", + "NavigationRailDestination", + "NavigationRailLabelType", + "OutlinedButton", + "PopupMenuButton", + "PopupMenuItem", + "PopupMenuPosition", + "ProgressBar", + "ProgressRing", + "Radio", + "RadioGroup", + "RangeSlider", + "OnReorderEvent", + "ReorderableListView", + "SearchBar", + "Segment", + "SegmentedButton", + "SelectionArea", + "Slider", + "SliderInteraction", + "DismissDirection", + "SnackBar", + "SnackBarAction", + "SnackBarBehavior", + "SubmenuButton", + "Switch", + "Tab", + "Tabs", + "TextButton", + "InputFilter", + "KeyboardType", + "NumbersOnlyInputFilter", + "TextCapitalization", + "TextField", + "TextOnlyInputFilter", + "TimePicker", + "TimePickerEntryMode", + "TimePickerEntryModeChangeEvent", + "Tooltip", + "TooltipTriggerMode", + "VerticalDivider", + "MultiView", + "OptionalPaddingValue", + "Padding", + "PaddingValue", + "AppLifecycleStateChangeEvent", + "KeyboardEvent", + "LoginEvent", + "MultiViewAddEvent", + "MultiViewRemoveEvent", + "Page", + "PageDisconnectedException", + "RouteChangeEvent", + "ViewPopEvent", + "context", + "PageMediaData", + "PageResizeEvent", + "PageView", + "Paint", + "PaintLinearGradient", + "PaintRadialGradient", + "PaintSweepGradient", + "PaintingStyle", + "QueryString", + "Ref", + "OnScrollEvent", + "ScrollDirection", + "ScrollType", + "ScrollableControl", + "BrowserContextMenu", + "FilePicker", + "FilePickerFileType", + "FilePickerUploadEvent", + "FilePickerUploadFile", + "HapticFeedback", + "Assertiveness", + "SemanticsService", + "Service", + "ShakeDetector", + "StoragePaths", + "Size", + "Rect", + "TemplateRoute", + "OptionalStrutStyle", + "OptionalTextBaseline", + "OptionalTextDecoration", + "OptionalTextDecorationStyle", + "OptionalTextOverflow", + "OptionalTextStyle", + "OptionalTextThemeStyle", + "TextBaseline", + "TextDecoration", + "TextDecorationStyle", + "TextOverflow", + "TextStyle", + "TextThemeStyle", + "AppBarTheme", + "BadgeTheme", + "BannerTheme", + "BottomAppBarTheme", + "BottomSheetTheme", + "CardTheme", + "CheckboxTheme", + "ChipTheme", + "ColorScheme", + "DataTableTheme", + "DatePickerTheme", + "DialogTheme", + "DividerTheme", + "ElevatedButtonTheme", + "ExpansionTileTheme", + "FilledButtonTheme", + "FloatingActionButtonTheme", + "IconButtonTheme", + "IconTheme", + "ListTileTheme", + "NavigationBarTheme", + "NavigationDrawerTheme", + "NavigationRailTheme", + "OutlinedButtonTheme", + "PageTransitionTheme", + "PageTransitionsTheme", + "PopupMenuTheme", + "ProgressIndicatorTheme", + "RadioTheme", + "ScrollbarTheme", + "SearchBarTheme", + "SearchViewTheme", + "SegmentedButtonTheme", + "SliderTheme", + "SnackBarTheme", + "SwitchTheme", + "SystemOverlayStyle", + "TabsTheme", + "TextButtonTheme", + "TextTheme", + "Theme", + "TimePickerTheme", + "TooltipTheme", + "Offset", + "OffsetValue", + "OptionalOffsetValue", + "OptionalRotateValue", + "OptionalScaleValue", + "Rotate", + "RotateValue", + "Scale", + "ScaleValue", + "AppLifecycleState", + "AppView", + "BlendMode", + "Brightness", + "ClipBehavior", + "ColorEnums", + "ColorValue", + "CrossAxisAlignment", + "FLET_APP", + "FLET_APP_HIDDEN", + "FLET_APP_WEB", + "FloatingActionButtonLocation", + "FontWeight", + "IconEnums", + "IconValue", + "IconValueOrControl", + "ImageFit", + "ImageRepeat", + "LabelPosition", + "Locale", + "LocaleConfiguration", + "MainAxisAlignment", + "MouseCursor", + "NotchShape", + "Number", + "OptionalBool", + "OptionalColorValue", + "OptionalFloat", + "OptionalInt", + "OptionalNumber", + "OptionalString", + "Orientation", + "PagePlatform", + "PointerDeviceType", + "ResponsiveNumber", + "ResponsiveRowBreakpoint", + "RouteUrlStrategy", + "ScrollMode", + "StrOrControl", + "StrokeCap", + "StrokeJoin", + "SupportsStr", + "TabAlignment", + "TextAlign", + "ThemeMode", + "UrlTarget", + "VerticalAlignment", + "VisualDensity", + "WEB_BROWSER", + "WebRenderer", + "UpdateBehavior", + "PubSubClient", + "PubSubHub", + "ScrollKey", + "ValueKey", + "Event", + "OptionalControlEventHandler", + "ControlEventHandler", + "EventHandler", + "OptionalEventHandler", + "EventControlType", + "TextSelectionChangeCause", + "TextSelectionChangeEvent", +] diff --git a/sdk/python/packages/flet/src/flet/ads/__init__.py b/sdk/python/packages/flet/src/flet/ads/__init__.py deleted file mode 100644 index edb108497..000000000 --- a/sdk/python/packages/flet/src/flet/ads/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from flet.core.ads.banner import BannerAd -from flet.core.ads.interstitial import InterstitialAd - -""" -from flet.core.ads.native import ( - NativeAd, - NativeAdTemplateStyle, - NativeAdTemplateTextStyle, - NativeTemplateFontStyle, - NativeAdTemplateType, -) -""" diff --git a/sdk/python/packages/flet/src/flet/app.py b/sdk/python/packages/flet/src/flet/app.py index 810a571f3..a6e73816d 100644 --- a/sdk/python/packages/flet/src/flet/app.py +++ b/sdk/python/packages/flet/src/flet/app.py @@ -1,16 +1,20 @@ import asyncio import concurrent.futures +import contextlib +import inspect import logging import os import signal import traceback +import warnings +from collections.abc import Awaitable from pathlib import Path -from typing import Optional +from typing import TYPE_CHECKING, Any, Callable, Optional, Union -import flet.version -from flet.core.event import Event -from flet.core.page import Page -from flet.core.types import AppView, WebRenderer +from flet.controls.page import Page, _session_page +from flet.controls.types import AppView, RouteUrlStrategy, WebRenderer +from flet.controls.update_behavior import UpdateBehavior +from flet.messaging.session import Session from flet.utils import ( get_bool_env_var, get_current_script_dir, @@ -20,31 +24,57 @@ is_pyodide, open_in_browser, ) +from flet.utils.deprecated import deprecated from flet.utils.pip import ( ensure_flet_desktop_package_installed, ensure_flet_web_package_installed, ) -import flet +if os.getenv("FLET_FORCE_WEB_SERVER"): + warnings.filterwarnings("ignore", module=r"websockets") + warnings.filterwarnings("ignore", module=r"uvicorn") -logger = logging.getLogger(flet.__name__) +logger = logging.getLogger("flet") +if TYPE_CHECKING: + from flet.controls.page import Page -def app( - target, - name="", - host=None, - port=0, + +@deprecated("Use run() instead.", version="0.70.0", show_parentheses=True) +def app(*args, **kwargs): + new_args = list(args) + if "target" in kwargs: + new_args.insert(0, kwargs["target"]) + return run(*new_args, **kwargs) + + +@deprecated("Use run() instead.", version="0.70.0", show_parentheses=True) +def app_async(*args, **kwargs): + new_args = list(args) + if "target" in kwargs: + new_args.insert(0, kwargs["target"]) + return run_async(*new_args, **kwargs) + + +def run( + main: Union[Callable[["Page"], Any], Callable[["Page"], Awaitable[Any]]], + before_main: Optional[ + Union[Callable[["Page"], None], Callable[["Page"], Awaitable[None]]] + ] = None, + name: str = "", + host: Optional[str] = None, + port: int = 0, view: Optional[AppView] = AppView.FLET_APP, - assets_dir="assets", - upload_dir=None, - web_renderer: WebRenderer = WebRenderer.CANVAS_KIT, - use_color_emoji=False, - route_url_strategy="path", - export_asgi_app=False, + assets_dir: Optional[str] = "assets", + upload_dir: Optional[str] = None, + web_renderer: WebRenderer = WebRenderer.AUTO, + route_url_strategy: RouteUrlStrategy = RouteUrlStrategy.PATH, + no_cdn: Optional[bool] = False, + export_asgi_app: Optional[bool] = False, + target=None, ): if is_pyodide(): - __run_pyodide(target) + __run_pyodide(main=main or target, before_main=before_main) return if export_asgi_app: @@ -52,18 +82,26 @@ def app( from flet_web.fastapi.serve_fastapi_web_app import get_fastapi_web_app return get_fastapi_web_app( - session_handler=target, + main=main or target, + before_main=before_main, page_name=__get_page_name(name), assets_dir=__get_assets_dir_path(assets_dir, relative_to_cwd=True), upload_dir=__get_upload_dir_path(upload_dir, relative_to_cwd=True), web_renderer=web_renderer, - use_color_emoji=use_color_emoji, route_url_strategy=route_url_strategy, + no_cdn=no_cdn, ) + if isinstance(web_renderer, str): + web_renderer = WebRenderer(web_renderer) + + if isinstance(route_url_strategy, str): + route_url_strategy = RouteUrlStrategy(route_url_strategy) + return asyncio.run( - app_async( - target=target, + run_async( + main=main or target, + before_main=before_main, name=name, host=host, port=port, @@ -71,26 +109,30 @@ def app( assets_dir=assets_dir, upload_dir=upload_dir, web_renderer=web_renderer, - use_color_emoji=use_color_emoji, route_url_strategy=route_url_strategy, + no_cdn=no_cdn, ) ) -async def app_async( - target, - name="", - host=None, - port=0, +async def run_async( + main: Union[Callable[["Page"], Any], Callable[["Page"], Awaitable[Any]]], + before_main: Optional[ + Union[Callable[["Page"], None], Callable[["Page"], Awaitable[None]]] + ] = None, + name: str = "", + host: Optional[str] = None, + port: int = 0, view: Optional[AppView] = AppView.FLET_APP, - assets_dir="assets", - upload_dir=None, - web_renderer: WebRenderer = WebRenderer.CANVAS_KIT, - use_color_emoji=False, - route_url_strategy="path", + assets_dir: Optional[str] = "assets", + upload_dir: Optional[str] = None, + web_renderer: WebRenderer = WebRenderer.AUTO, + route_url_strategy: RouteUrlStrategy = RouteUrlStrategy.PATH, + no_cdn: Optional[bool] = False, + target=None, ): if is_pyodide(): - __run_pyodide(target) + __run_pyodide(main=main or target, before_main=before_main) return if isinstance(view, str): @@ -99,6 +141,9 @@ async def app_async( if isinstance(web_renderer, str): web_renderer = WebRenderer(web_renderer) + if isinstance(route_url_strategy, str): + route_url_strategy = RouteUrlStrategy(route_url_strategy) + force_web_server = get_bool_env_var("FLET_FORCE_WEB_SERVER") or is_linux_server() if force_web_server: view = AppView.WEB_BROWSER @@ -149,20 +194,22 @@ def exit_gracefully(signum, frame): conn = ( await __run_socket_server( port=port, - session_handler=target, + main=main or target, + before_main=before_main, blocking=is_embedded(), ) if is_socket_server else await __run_web_server( - session_handler=target, + main=main or target, + before_main=before_main, host=host, port=port, page_name=page_name, assets_dir=assets_dir, upload_dir=upload_dir, web_renderer=web_renderer, - use_color_emoji=use_color_emoji, route_url_strategy=route_url_strategy, + no_cdn=no_cdn, blocking=(view == AppView.WEB_BROWSER or view is None or force_web_server), on_startup=on_app_startup, ) @@ -191,76 +238,70 @@ def exit_gracefully(signum, frame): assets_dir if view != AppView.FLET_APP_WEB else None, view == AppView.FLET_APP_HIDDEN, ) - try: + with contextlib.suppress(Exception): await fvp.wait() - except: - pass close_flet_view(pid_file) elif url_prefix and is_socket_server: on_app_startup(conn.page_url) - try: + with contextlib.suppress(KeyboardInterrupt): await terminate.wait() - except KeyboardInterrupt: - pass finally: await conn.close() -async def __run_socket_server(port=0, session_handler=None, blocking=False): - from flet.flet_socket_server import FletSocketServer - - uds_path = os.getenv("FLET_SERVER_UDS_PATH") - - executor = concurrent.futures.ThreadPoolExecutor() - - async def on_event(e): - if e.sessionID in conn.sessions: - await conn.sessions[e.sessionID].on_event_async( - Event(e.eventTarget, e.eventName, e.eventData) - ) - if e.eventTarget == "page" and e.eventName == "close": - logger.info(f"Session closed: {e.sessionID}") - page = conn.sessions.pop(e.sessionID) - page._close() - del page - - async def on_session_created(session_data): - page = Page( - conn, - session_data.sessionID, - executor=executor, - loop=asyncio.get_running_loop(), - ) - await page.fetch_page_details_async() - conn.sessions[session_data.sessionID] = page +def __get_on_session_created(main): + async def on_session_created(session: Session): logger.info("App session started") try: - assert session_handler is not None - if asyncio.iscoroutinefunction(session_handler): - await session_handler(page) + assert main is not None + _session_page.set(session.page) + UpdateBehavior.reset() + if asyncio.iscoroutinefunction(main): + await main(session.page) + + elif inspect.isasyncgenfunction(main): + async for _ in main(session.page): + if UpdateBehavior.auto_update_enabled(): + await session.auto_update(session.page) + + elif inspect.isgeneratorfunction(main): + for _ in main(session.page): + if UpdateBehavior.auto_update_enabled(): + await session.auto_update(session.page) else: - # run in thread pool - await asyncio.get_running_loop().run_in_executor( - executor, session_handler, page - ) + # run synchronously + main(session.page) + + if UpdateBehavior.auto_update_enabled(): + await session.auto_update(session.page) except Exception as e: print( - f"Unhandled error processing page session {page.session_id}:", + f"Unhandled error processing page session {session.id}:", traceback.format_exc(), ) - page.error(f"There was an error while processing your request: {e}") + session.error(f"The application encountered an error: {e}") + + return on_session_created + + +async def __run_socket_server(port=0, main=None, before_main=None, blocking=False): + from flet.messaging.flet_socket_server import FletSocketServer + + uds_path = os.getenv("FLET_SERVER_UDS_PATH") + + executor = concurrent.futures.ThreadPoolExecutor() conn = FletSocketServer( loop=asyncio.get_running_loop(), port=port, uds_path=uds_path, - on_event=on_event, - on_session_created=on_session_created, + on_session_created=__get_on_session_created(main), + before_main=before_main, blocking=blocking, executor=executor, ) @@ -269,15 +310,16 @@ async def on_session_created(session_data): async def __run_web_server( - session_handler, + main, + before_main, host, port, page_name, assets_dir, upload_dir, web_renderer: Optional[WebRenderer], - use_color_emoji, route_url_strategy, + no_cdn, blocking, on_startup, ): @@ -291,12 +333,13 @@ async def __run_web_server( logger.info(f"Starting Flet web server on port {port}...") - log_level = logging.getLogger(flet.__name__).getEffectiveLevel() + log_level = logging.getLogger("flet").getEffectiveLevel() if log_level == logging.CRITICAL or log_level == logging.NOTSET: log_level = logging.FATAL return await serve_fastapi_web_app( - session_handler, + main, + before_main=before_main, host=host, url_host=url_host, port=port, @@ -304,48 +347,19 @@ async def __run_web_server( assets_dir=assets_dir, upload_dir=upload_dir, web_renderer=web_renderer, - use_color_emoji=use_color_emoji, route_url_strategy=route_url_strategy, + no_cdn=no_cdn, blocking=blocking, on_startup=on_startup, log_level=logging.getLevelName(log_level).lower(), ) -def __run_pyodide(target): - import flet_js - from flet.pyodide_connection import PyodideConnection - - async def on_event(e): - if e.sessionID in conn.sessions: - await conn.sessions[e.sessionID].on_event_async( - Event(e.eventTarget, e.eventName, e.eventData) - ) - if e.eventTarget == "page" and e.eventName == "close": - logger.info(f"Session closed: {e.sessionID}") - del conn.sessions[e.sessionID] - - async def on_session_created(session_data): - page = Page(conn, session_data.sessionID, loop=asyncio.get_running_loop()) - await page.fetch_page_details_async() - conn.sessions[session_data.sessionID] = page - logger.info("App session started") - try: - assert target is not None - if asyncio.iscoroutinefunction(target): - await target(page) - else: - target(page) - except Exception as e: - print( - f"Unhandled error processing page session {page.session_id}:", - traceback.format_exc(), - ) - page.error(f"There was an error while processing your request: {e}") +def __run_pyodide(main=None, before_main=None): + from flet.messaging.pyodide_connection import PyodideConnection - conn = PyodideConnection( - on_event=on_event, - on_session_created=on_session_created, + PyodideConnection( + on_session_created=__get_on_session_created(main), before_main=before_main ) @@ -377,11 +391,10 @@ def __get_assets_dir_path(assets_dir: Optional[str], relative_to_cwd=False): def __get_upload_dir_path(upload_dir: Optional[str], relative_to_cwd=False): - if upload_dir: - if not Path(upload_dir).is_absolute(): - upload_dir = str( - Path(os.getcwd() if relative_to_cwd else get_current_script_dir()) - .joinpath(upload_dir) - .resolve() - ) + if upload_dir and not Path(upload_dir).is_absolute(): + upload_dir = str( + Path(os.getcwd() if relative_to_cwd else get_current_script_dir()) + .joinpath(upload_dir) + .resolve() + ) return upload_dir diff --git a/sdk/python/packages/flet/src/flet/auth/__init__.py b/sdk/python/packages/flet/src/flet/auth/__init__.py index ffeb327a5..e146fcbff 100644 --- a/sdk/python/packages/flet/src/flet/auth/__init__.py +++ b/sdk/python/packages/flet/src/flet/auth/__init__.py @@ -3,3 +3,5 @@ from flet.auth.oauth_provider import OAuthProvider from flet.auth.oauth_token import OAuthToken from flet.auth.user import User + +__all__ = ["Authorization", "Group", "OAuthProvider", "OAuthToken", "User"] diff --git a/sdk/python/packages/flet/src/flet/auth/authorization.py b/sdk/python/packages/flet/src/flet/auth/authorization.py index a33cdc4e0..1ee1812f8 100644 --- a/sdk/python/packages/flet/src/flet/auth/authorization.py +++ b/sdk/python/packages/flet/src/flet/auth/authorization.py @@ -1,234 +1,12 @@ -import asyncio -import json -import secrets -import threading -import time -from typing import List, Optional, Tuple - -import httpx -from oauthlib.oauth2 import WebApplicationClient -from oauthlib.oauth2.rfc6749.tokens import OAuth2Token - -from flet.auth.oauth_provider import OAuthProvider -from flet.auth.oauth_token import OAuthToken -from flet.auth.user import User -from flet.core.locks import AsyncNopeLock, NopeLock -from flet.utils import is_asyncio -from flet.version import version - - class Authorization: - def __init__( - self, - provider: OAuthProvider, - fetch_user: bool, - fetch_groups: bool, - scope: Optional[List[str]] = None, - ) -> None: - self.fetch_user = fetch_user - self.fetch_groups = fetch_groups - self.scope = scope if scope is not None else [] - self.provider = provider - self.__token: Optional[OAuthToken] = None - self.user: Optional[User] = None - self.__lock = threading.Lock() if not is_asyncio() else NopeLock() - self.__async_lock = asyncio.Lock() if is_asyncio() else AsyncNopeLock() - - # fix scopes - self.scope.extend(self.provider.scopes) - if self.fetch_user: - for s in self.provider.user_scopes: - if s not in self.scope: - self.scope.append(s) - if self.fetch_groups: - for s in self.provider.group_scopes: - if s not in self.scope: - self.scope.append(s) - - def dehydrate_token(self, saved_token: str): - self.__token = OAuthToken.from_json(saved_token) - self.__refresh_token() - self.__fetch_user_and_groups() - async def dehydrate_token_async(self, saved_token: str): - self.__token = OAuthToken.from_json(saved_token) - await self.__refresh_token_async() - await self.__fetch_user_and_groups_async() - - # token - @property - def token(self) -> Optional[OAuthToken]: - with self.__lock: - self.__refresh_token() - return self.__token - - # token_async - @property - async def token_async(self) -> Optional[OAuthToken]: - async with self.__async_lock: - await self.__refresh_token_async() - return self.__token + raise NotImplementedError() - def get_authorization_data(self) -> Tuple[str, str]: - self.state = secrets.token_urlsafe(16) - client = WebApplicationClient(self.provider.client_id) - authorization_url = client.prepare_request_uri( - self.provider.authorization_endpoint, - self.provider.redirect_url, - scope=self.scope, - state=self.state, - code_challenge=self.provider.code_challenge, - code_challenge_method=self.provider.code_challenge_method, - ) - return authorization_url, self.state + async def get_token_async(self): + raise NotImplementedError() - def request_token(self, code: str): - req = self.__get_request_token_request(code) - with httpx.Client(follow_redirects=True) as client: - resp = client.send(req) - resp.raise_for_status() - client = WebApplicationClient(self.provider.client_id) - t = client.parse_request_body_response(resp.text) - self.__token = self.__convert_token(t) - self.__fetch_user_and_groups() + def get_authorization_data(self) -> tuple[str, str]: + raise NotImplementedError() async def request_token_async(self, code: str): - req = self.__get_request_token_request(code) - async with httpx.AsyncClient(follow_redirects=True) as client: - resp = await client.send(req) - resp.raise_for_status() - client = WebApplicationClient(self.provider.client_id) - t = client.parse_request_body_response(resp.text) - self.__token = self.__convert_token(t) - await self.__fetch_user_and_groups_async() - - def __get_request_token_request(self, code: str): - client = WebApplicationClient(self.provider.client_id) - data = client.prepare_request_body( - code=code, - redirect_uri=self.provider.redirect_url, - client_secret=self.provider.client_secret, - include_client_id=True, - code_verifier=self.provider.code_verifier, - ) - headers = self.__get_default_headers() - headers["content-type"] = "application/x-www-form-urlencoded" - return httpx.Request( - "POST", self.provider.token_endpoint, content=data, headers=headers - ) - - def __fetch_user_and_groups(self): - assert self.__token is not None - if self.fetch_user: - self.user = self.provider._fetch_user(self.__token.access_token) - if self.user is None and self.provider.user_endpoint is not None: - if self.provider.user_id_fn is None: - raise Exception( - "user_id_fn must be specified too if user_endpoint is not None" - ) - self.user = self.__get_user() - if self.fetch_groups and self.user is not None: - self.user.groups = self.provider._fetch_groups( - self.__token.access_token - ) - - async def __fetch_user_and_groups_async(self): - assert self.__token is not None - if self.fetch_user: - self.user = await self.provider._fetch_user_async(self.__token.access_token) - if self.user is None and self.provider.user_endpoint is not None: - if self.provider.user_id_fn is None: - raise Exception( - "user_id_fn must be specified too if user_endpoint is not None" - ) - self.user = await self.__get_user_async() - if self.fetch_groups and self.user is not None: - self.user.groups = await self.provider._fetch_groups_async( - self.__token.access_token - ) - - def __convert_token(self, t: OAuth2Token): - return OAuthToken( - access_token=t["access_token"], - scope=t.get("scope"), - token_type=t.get("token_type"), - expires_in=t.get("expires_in"), - expires_at=t.get("expires_at"), - refresh_token=t.get("refresh_token"), - ) - - def __refresh_token(self): - refresh_req = self.__get_refresh_token_request() - if refresh_req: - with httpx.Client(follow_redirects=True) as client: - refresh_resp = client.send(refresh_req) - self.__complete_refresh_token_request(refresh_resp) - - async def __refresh_token_async(self): - refresh_req = self.__get_refresh_token_request() - if refresh_req: - async with httpx.AsyncClient(follow_redirects=True) as client: - refresh_resp = await client.send(refresh_req) - self.__complete_refresh_token_request(refresh_resp) - - def __get_refresh_token_request(self): - if ( - self.__token is None - or self.__token.expires_at is None - or self.__token.refresh_token is None - or time.time() < self.__token.expires_at - ): - return None - - assert self.__token is not None - client = WebApplicationClient(self.provider.client_id) - data = client.prepare_refresh_body( - client_id=self.provider.client_id, - client_secret=self.provider.client_secret, - refresh_token=self.__token.refresh_token, - redirect_uri=self.provider.redirect_url, - ) - headers = self.__get_default_headers() - headers["content-type"] = "application/x-www-form-urlencoded" - return httpx.Request( - "POST", url=self.provider.token_endpoint, content=data, headers=headers - ) - - def __complete_refresh_token_request(self, refresh_resp): - refresh_resp.raise_for_status() - assert self.__token is not None - client = WebApplicationClient(self.provider.client_id) - t = client.parse_request_body_response(refresh_resp.text) - if t.get("refresh_token") is None: - t["refresh_token"] = self.__token.refresh_token - self.__token = self.__convert_token(t) - - def __get_user(self): - user_req = self.__get_user_request() - with httpx.Client() as client: - user_resp = client.send(user_req) - return self.__complete_user_request(user_resp) - - async def __get_user_async(self): - user_req = self.__get_user_request() - async with httpx.AsyncClient(follow_redirects=True) as client: - user_resp = await client.send(user_req) - return self.__complete_user_request(user_resp) - - def __get_user_request(self): - assert self.token is not None - assert self.provider.user_endpoint is not None - headers = self.__get_default_headers() - headers["Authorization"] = f"Bearer {self.token.access_token}" - return httpx.Request("GET", self.provider.user_endpoint, headers=headers) - - def __complete_user_request(self, user_resp): - user_resp.raise_for_status() - assert self.provider.user_id_fn is not None - uj = json.loads(user_resp.text) - return User(uj, str(self.provider.user_id_fn(uj))) - - def __get_default_headers(self): - return { - "User-Agent": f"Flet/{version}", - } + raise NotImplementedError() diff --git a/sdk/python/packages/flet/src/flet/auth/authorization_service.py b/sdk/python/packages/flet/src/flet/auth/authorization_service.py new file mode 100644 index 000000000..0b337294e --- /dev/null +++ b/sdk/python/packages/flet/src/flet/auth/authorization_service.py @@ -0,0 +1,162 @@ +import json +import secrets +import time +from typing import Optional + +import httpx +from oauthlib.oauth2 import WebApplicationClient +from oauthlib.oauth2.rfc6749.tokens import OAuth2Token + +from flet.auth.authorization import Authorization +from flet.auth.oauth_provider import OAuthProvider +from flet.auth.oauth_token import OAuthToken +from flet.auth.user import User +from flet.version import version + + +class AuthorizationService(Authorization): + def __init__( + self, + provider: OAuthProvider, + fetch_user: bool, + fetch_groups: bool, + scope: Optional[list[str]] = None, + ) -> None: + self.fetch_user = fetch_user + self.fetch_groups = fetch_groups + self.scope = scope if scope is not None else [] + self.provider = provider + self.__token: Optional[OAuthToken] = None + self.user: Optional[User] = None + + # fix scopes + self.scope.extend(self.provider.scopes) + if self.fetch_user: + for s in self.provider.user_scopes: + if s not in self.scope: + self.scope.append(s) + if self.fetch_groups: + for s in self.provider.group_scopes: + if s not in self.scope: + self.scope.append(s) + + async def dehydrate_token_async(self, saved_token: str): + self.__token = OAuthToken.from_json(saved_token) + await self.__refresh_token_async() + await self.__fetch_user_and_groups_async() + + # token_async + async def get_token_async(self): + await self.__refresh_token_async() + return self.__token + + def get_authorization_data(self) -> tuple[str, str]: + self.state = secrets.token_urlsafe(16) + client = WebApplicationClient(self.provider.client_id) + authorization_url = client.prepare_request_uri( + self.provider.authorization_endpoint, + self.provider.redirect_url, + scope=self.scope, + state=self.state, + code_challenge=self.provider.code_challenge, + code_challenge_method=self.provider.code_challenge_method, + ) + return authorization_url, self.state + + async def request_token_async(self, code: str): + client = WebApplicationClient(self.provider.client_id) + data = client.prepare_request_body( + code=code, + redirect_uri=self.provider.redirect_url, + client_secret=self.provider.client_secret, + include_client_id=True, + code_verifier=self.provider.code_verifier, + ) + headers = self.__get_default_headers() + headers["content-type"] = "application/x-www-form-urlencoded" + req = httpx.Request( + "POST", self.provider.token_endpoint, content=data, headers=headers + ) + async with httpx.AsyncClient(follow_redirects=True) as client: + resp = await client.send(req) + resp.raise_for_status() + client = WebApplicationClient(self.provider.client_id) + t = client.parse_request_body_response(resp.text) + self.__token = self.__convert_token(t) + await self.__fetch_user_and_groups_async() + + async def __fetch_user_and_groups_async(self): + assert self.__token is not None + if self.fetch_user: + self.user = await self.provider._fetch_user_async(self.__token.access_token) + if self.user is None and self.provider.user_endpoint is not None: + if self.provider.user_id_fn is None: + raise Exception( + "user_id_fn must be specified too if user_endpoint is not None" + ) + self.user = await self.__get_user_async() + if self.fetch_groups and self.user is not None: + self.user.groups = await self.provider._fetch_groups_async( + self.__token.access_token + ) + + def __convert_token(self, t: OAuth2Token): + return OAuthToken( + access_token=t["access_token"], + scope=t.get("scope"), + token_type=t.get("token_type"), + expires_in=t.get("expires_in"), + expires_at=t.get("expires_at"), + refresh_token=t.get("refresh_token"), + ) + + async def __refresh_token_async(self): + if ( + self.__token is None + or self.__token.expires_at is None + or self.__token.refresh_token is None + or time.time() < self.__token.expires_at + ): + return None + + assert self.__token is not None + client = WebApplicationClient(self.provider.client_id) + data = client.prepare_refresh_body( + client_id=self.provider.client_id, + client_secret=self.provider.client_secret, + refresh_token=self.__token.refresh_token, + redirect_uri=self.provider.redirect_url, + ) + headers = self.__get_default_headers() + headers["content-type"] = "application/x-www-form-urlencoded" + refresh_req = httpx.Request( + "POST", url=self.provider.token_endpoint, content=data, headers=headers + ) + if refresh_req: + async with httpx.AsyncClient(follow_redirects=True) as client: + refresh_resp = await client.send(refresh_req) + refresh_resp.raise_for_status() + assert self.__token is not None + client = WebApplicationClient(self.provider.client_id) + t = client.parse_request_body_response(refresh_resp.text) + if t.get("refresh_token") is None: + t["refresh_token"] = self.__token.refresh_token + self.__token = self.__convert_token(t) + + async def __get_user_async(self): + assert self.token is not None + assert self.provider.user_endpoint is not None + headers = self.__get_default_headers() + headers["Authorization"] = f"Bearer {self.token.access_token}" + user_req = httpx.Request("GET", self.provider.user_endpoint, headers=headers) + async with httpx.AsyncClient(follow_redirects=True) as client: + user_resp = await client.send(user_req) + user_resp.raise_for_status() + assert self.provider.user_id_fn is not None + uj = json.loads(user_resp.text) + return User(uj, str(self.provider.user_id_fn(uj))) + + def __get_default_headers(self): + return { + "User-Agent": f"Flet/{version}", + } diff --git a/sdk/python/packages/flet/src/flet/auth/oauth_provider.py b/sdk/python/packages/flet/src/flet/auth/oauth_provider.py index d8c8c0c72..5ebd4f831 100644 --- a/sdk/python/packages/flet/src/flet/auth/oauth_provider.py +++ b/sdk/python/packages/flet/src/flet/auth/oauth_provider.py @@ -38,14 +38,8 @@ def __init__( def _name(self): raise Exception("Not implemented") - def _fetch_groups(self, access_token: str) -> List[Group]: - return [] - async def _fetch_groups_async(self, access_token: str) -> List[Group]: return [] - def _fetch_user(self, access_token: str) -> Optional[User]: - return None - async def _fetch_user_async(self, access_token: str) -> Optional[User]: return None diff --git a/sdk/python/packages/flet/src/flet/auth/oauth_token.py b/sdk/python/packages/flet/src/flet/auth/oauth_token.py index b65492ac1..6f7a62032 100644 --- a/sdk/python/packages/flet/src/flet/auth/oauth_token.py +++ b/sdk/python/packages/flet/src/flet/auth/oauth_token.py @@ -1,14 +1,14 @@ import json -from typing import List, Optional +from typing import Optional -from flet.core.embed_json_encoder import EmbedJsonEncoder +from flet.controls.embed_json_encoder import EmbedJsonEncoder class OAuthToken: def __init__( self, access_token: str, - scope: Optional[List[str]] = None, + scope: Optional[list[str]] = None, token_type: Optional[str] = None, expires_in: Optional[int] = None, expires_at: Optional[float] = None, diff --git a/sdk/python/packages/flet/src/flet/auth/providers/__init__.py b/sdk/python/packages/flet/src/flet/auth/providers/__init__.py index 0d434e441..28638f706 100644 --- a/sdk/python/packages/flet/src/flet/auth/providers/__init__.py +++ b/sdk/python/packages/flet/src/flet/auth/providers/__init__.py @@ -2,3 +2,10 @@ from flet.auth.providers.azure_oauth_provider import AzureOAuthProvider from flet.auth.providers.github_oauth_provider import GitHubOAuthProvider from flet.auth.providers.google_oauth_provider import GoogleOAuthProvider + +__all__ = [ + "Auth0OAuthProvider", + "AzureOAuthProvider", + "GitHubOAuthProvider", + "GoogleOAuthProvider", +] diff --git a/sdk/python/packages/flet/src/flet/auth/providers/github_oauth_provider.py b/sdk/python/packages/flet/src/flet/auth/providers/github_oauth_provider.py index f65ee6981..3eda01b15 100644 --- a/sdk/python/packages/flet/src/flet/auth/providers/github_oauth_provider.py +++ b/sdk/python/packages/flet/src/flet/auth/providers/github_oauth_provider.py @@ -20,74 +20,53 @@ def __init__(self, client_id: str, client_secret: str, redirect_url: str) -> Non group_scopes=["read:org"], ) - def _fetch_groups(self, access_token: str) -> List[Group]: - with httpx.Client(follow_redirects=True) as client: - teams_resp = client.send(self.__get_user_teams_request(access_token)) - return self.__complete_fetch_groups(teams_resp) - async def _fetch_groups_async(self, access_token: str) -> List[Group]: async with httpx.AsyncClient(follow_redirects=True) as client: - teams_resp = await client.send(self.__get_user_teams_request(access_token)) - return self.__complete_fetch_groups(teams_resp) - - def __get_user_teams_request(self, access_token): - return httpx.Request( - "GET", - "https://api.github.com/user/teams", - headers=self.__get_client_headers(access_token), - ) - - def __complete_fetch_groups(self, teams_resp): - teams_resp.raise_for_status() - groups = [] - tj = json.loads(teams_resp.text) - for t in tj: - groups.append( - Group( - t, - name=t["name"], + teams_resp = await client.send( + httpx.Request( + "GET", + "https://api.github.com/user/teams", + headers=self.__get_client_headers(access_token), ) ) - return groups - - def _fetch_user(self, access_token: str) -> Optional[User]: - user_req, emails_req = self.__get_user_details_requests(access_token) - with httpx.Client(follow_redirects=True) as client: - user_resp = client.send(user_req) - emails_resp = client.send(emails_req) - return self.__complete_fetch_user_details(user_resp, emails_resp) + teams_resp.raise_for_status() + groups = [] + tj = json.loads(teams_resp.text) + for t in tj: + groups.append( + Group( + t, + name=t["name"], + ) + ) + return groups async def _fetch_user_async(self, access_token: str) -> Optional[User]: - user_req, emails_req = self.__get_user_details_requests(access_token) async with httpx.AsyncClient(follow_redirects=True) as client: - user_resp = await client.send(user_req) - emails_resp = await client.send(emails_req) - return self.__complete_fetch_user_details(user_resp, emails_resp) - - def __get_user_details_requests(self, access_token): - return ( - httpx.Request( - "GET", - "https://api.github.com/user", - headers=self.__get_client_headers(access_token), - ), - httpx.Request( - "GET", - "https://api.github.com/user/emails", - headers=self.__get_client_headers(access_token), - ), - ) + user_resp = await client.send( + httpx.Request( + "GET", + "https://api.github.com/user", + headers=self.__get_client_headers(access_token), + ) + ) + user_resp.raise_for_status() + uj = json.loads(user_resp.text) - def __complete_fetch_user_details(self, user_resp, emails_resp): - user_resp.raise_for_status() - emails_resp.raise_for_status() - uj = json.loads(user_resp.text) - ej = json.loads(emails_resp.text) - for e in ej: - if e["primary"]: - uj["email"] = e["email"] - break - return User(uj, id=str(uj["id"])) + emails_resp = await client.send( + httpx.Request( + "GET", + "https://api.github.com/user/emails", + headers=self.__get_client_headers(access_token), + ) + ) + emails_resp.raise_for_status() + ej = json.loads(emails_resp.text) + for e in ej: + if e["primary"]: + uj["email"] = e["email"] + break + return User(uj, id=str(uj["id"])) def __get_client_headers(self, access_token): return { diff --git a/sdk/python/packages/flet/src/flet/canvas/__init__.py b/sdk/python/packages/flet/src/flet/canvas/__init__.py index bf541dd23..7cea3ddce 100644 --- a/sdk/python/packages/flet/src/flet/canvas/__init__.py +++ b/sdk/python/packages/flet/src/flet/canvas/__init__.py @@ -1,12 +1,29 @@ -from flet.core.canvas.arc import Arc -from flet.core.canvas.canvas import Canvas, CanvasResizeEvent -from flet.core.canvas.circle import Circle -from flet.core.canvas.color import Color -from flet.core.canvas.fill import Fill -from flet.core.canvas.line import Line -from flet.core.canvas.oval import Oval -from flet.core.canvas.path import Path -from flet.core.canvas.points import PointMode, Points -from flet.core.canvas.rect import Rect -from flet.core.canvas.shadow import Shadow -from flet.core.canvas.text import Text +from flet.controls.core.canvas.arc import Arc +from flet.controls.core.canvas.canvas import Canvas, CanvasResizeEvent +from flet.controls.core.canvas.circle import Circle +from flet.controls.core.canvas.color import Color +from flet.controls.core.canvas.fill import Fill +from flet.controls.core.canvas.line import Line +from flet.controls.core.canvas.oval import Oval +from flet.controls.core.canvas.path import Path +from flet.controls.core.canvas.points import PointMode, Points +from flet.controls.core.canvas.rect import Rect +from flet.controls.core.canvas.shadow import Shadow +from flet.controls.core.canvas.text import Text + +__all__ = [ + "Arc", + "Canvas", + "CanvasResizeEvent", + "Circle", + "Color", + "Fill", + "Line", + "Oval", + "Path", + "PointMode", + "Points", + "Rect", + "Shadow", + "Text", +] diff --git a/sdk/python/packages/flet/src/flet/controls/__init__.py b/sdk/python/packages/flet/src/flet/controls/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sdk/python/packages/flet/src/flet/controls/adaptive_control.py b/sdk/python/packages/flet/src/flet/controls/adaptive_control.py new file mode 100644 index 000000000..9b7e88fd0 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/adaptive_control.py @@ -0,0 +1,53 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control import Control + +__all__ = ["AdaptiveControl"] + + +@control(kw_only=True) +class AdaptiveControl(Control): + """ + Adaptive controls are either Material design controls that have their Cupertino + analogs or container controls. + """ + adaptive: Optional[bool] = None + """ + `adaptive` property can be specified for a control in the following cases: + + * A control has matching Cupertino control with similar functionality/presentation + and graphics as expected on iOS/macOS. In this case, if `adaptive` is `True`, + either Material or Cupertino control will be created depending on the target + platform. + + These controls have their Cupertino analogs and `adaptive` property: + * [`AlertDialog`](https://flet.dev/docs/controls/alertdialog) + * [`AppBar`](https://flet.dev/docs/controls/appbar) + * [`Checkbox`](https://flet.dev/docs/controls/checkbox) + * [`ListTile`](https://flet.dev/docs/controls/listtile) + * [`NavigationBar`](https://flet.dev/docs/controls/navigationbar) + * [`Radio`](https://flet.dev/docs/controls/radio) + * [`Slider`](https://flet.dev/docs/controls/slider) + * [`Switch`](https://flet.dev/docs/controls/switch) + + * A control has child controls. In this case `adaptive` property value is passed on + to its children that don't have their `adaptive` property set. + + The following container controls have `adaptive` property: + * [`Card`](https://flet.dev/docs/controls/card) + * [`Column`](https://flet.dev/docs/controls/column) + * [`Container`](https://flet.dev/docs/controls/container) + * [`Dismissible`](https://flet.dev/docs/controls/dismissible) + * [`ExpansionPanel`](https://flet.dev/docs/controls/expansionpanel) + * [`FletApp`](https://flet.dev/docs/controls/fletapp) + * [`GestureDetector`](https://flet.dev/docs/controls/gesturedetector) + * [`GridView`](https://flet.dev/docs/controls/gridview) + * [`ListView`](https://flet.dev/docs/controls/listview) + * [`Page`](https://flet.dev/docs/controls/page) + * [`Row`](https://flet.dev/docs/controls/row) + * [`SafeArea`](https://flet.dev/docs/controls/safearea) + * [`Stack`](https://flet.dev/docs/controls/stack) + * [`Tabs`](https://flet.dev/docs/controls/tabs) + * [`View`](https://flet.dev/docs/controls/view) + """ diff --git a/sdk/python/packages/flet/src/flet/controls/alignment.py b/sdk/python/packages/flet/src/flet/controls/alignment.py new file mode 100644 index 000000000..fc287f00f --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/alignment.py @@ -0,0 +1,104 @@ +from dataclasses import dataclass +from enum import Enum +from typing import Optional + +from flet.controls.types import Number + +__all__ = [ + "Alignment", + "Axis", + "OptionalAlignment", + "OptionalAxis", +] + + +class Axis(Enum): + HORIZONTAL = "horizontal" + VERTICAL = "vertical" + + +@dataclass +class Alignment: + """ + Used to define an alignment relative to the center. + """ + + x: Number + """ + Represents the horizontal distance from the center. It's value ranges between + `-1.0` and `1.0`. + """ + + y: Number + """ + Represents the vertical distance from the center. It's value ranges between + `-1.0` and `1.0`. + """ + + @classmethod + def bottom_center(cls) -> "Alignment": + """ + Represents the bottom center and is equivalent to `Alignment(0.0, 1.0)`. + """ + return Alignment(0, 1) + + @classmethod + def bottom_left(cls) -> "Alignment": + """ + Represents the bottom left corner and is equivalent to `Alignment(-1.0, 1.0)`. + """ + return Alignment(-1, 1) + + @classmethod + def bottom_right(cls) -> "Alignment": + """ + Represents the bottom right corner and is equivalent to `Alignment(1.0, 1.0)`. + """ + return Alignment(1, 1) + + @classmethod + def center(cls) -> "Alignment": + """ + Represents the center and is equivalent to `Alignment(0.0, 0.0)`. + """ + return Alignment(0, 0) + + @classmethod + def center_left(cls) -> "Alignment": + """ + Represents the center left and is equivalent to `Alignment(-1.0, 0.0)`. + """ + return Alignment(-1, 0) + + @classmethod + def center_right(cls) -> "Alignment": + """ + Represents the center right and is equivalent to `Alignment(1.0, 0.0)`. + """ + return Alignment(1, 0) + + @classmethod + def top_center(cls) -> "Alignment": + """ + Represents the top center and is equivalent to `Alignment(0.0, -1.0)`. + """ + return Alignment(0, -1) + + @classmethod + def top_left(cls) -> "Alignment": + """ + Represents the top left corner and is equivalent to `Alignment(-1.0, -1.0)`. + """ + return Alignment(-1, -1) + + @classmethod + def top_right(cls) -> "Alignment": + """ + Represents the top right corner and is equivalent to `Alignment(1.0, -1.0)`. + """ + return Alignment(1, -1) + + +# Typing +OptionalAlignment = Optional[Alignment] +OptionalAxis = Optional[Axis] diff --git a/sdk/python/packages/flet/src/flet/core/animation.py b/sdk/python/packages/flet/src/flet/controls/animation.py similarity index 51% rename from sdk/python/packages/flet/src/flet/core/animation.py rename to sdk/python/packages/flet/src/flet/controls/animation.py index 3f35fca9b..823678e1f 100644 --- a/sdk/python/packages/flet/src/flet/core/animation.py +++ b/sdk/python/packages/flet/src/flet/controls/animation.py @@ -1,14 +1,19 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum from typing import Optional, Union -from flet.core.types import DurationValue -from flet.utils import deprecated +from flet.controls.duration import OptionalDurationValue, DurationValue, Duration -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal +__all__ = [ + "Animation", + "AnimationCurve", + "AnimationStyle", + "AnimationValue", + "OptionalAnimation", + "OptionalAnimationCurve", + "OptionalAnimationStyle", + "OptionalAnimationValue", +] class AnimationCurve(Enum): @@ -58,23 +63,50 @@ class AnimationCurve(Enum): @dataclass class Animation: - duration: DurationValue = None - curve: Optional[AnimationCurve] = None + duration: DurationValue = field(default_factory=lambda: Duration()) + """ + The duration of the animation. + """ + + curve: AnimationCurve = AnimationCurve.LINEAR + """ + The curve to use for the animation. + """ @dataclass class AnimationStyle: - duration: Optional[DurationValue] = None - reverse_duration: Optional[DurationValue] = None - curve: Optional[AnimationCurve] = None - reverse_curve: Optional[AnimationCurve] = None + duration: OptionalDurationValue = None + """ + The duration of the animation. + + Value is of type [`DurationValue`](https://flet.dev/docs/reference/types/aliases#durationvalue). + """ + + reverse_duration: OptionalDurationValue = None + """ + The duration of the reverse animation. + + Value is of type [`DurationValue`](https://flet.dev/docs/reference/types/aliases#durationvalue). + """ + + curve: "OptionalAnimationCurve" = None + """ + The curve to use for the animation. + Value is of type [`AnimationCurve`](https://flet.dev/docs/reference/types/animationcurve). + """ -@deprecated( - reason="Use Animation class instead.", version="0.26.0", delete_version="0.29.0" -) -def implicit(duration: DurationValue, curve: Optional[AnimationCurve] = None): - return Animation(duration=duration, curve=curve) + reverse_curve: "OptionalAnimationCurve" = None + """ + The curve to use for the reverse animation. + Value is of type [`AnimationCurve`](https://flet.dev/docs/reference/types/animationcurve). + """ + AnimationValue = Union[bool, int, Animation] +OptionalAnimationValue = Optional[AnimationValue] +OptionalAnimationStyle = Optional[AnimationStyle] +OptionalAnimation = Optional[Animation] +OptionalAnimationCurve = Optional[AnimationCurve] diff --git a/sdk/python/packages/flet/src/flet/controls/base_control.py b/sdk/python/packages/flet/src/flet/controls/base_control.py new file mode 100644 index 000000000..bc8281105 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/base_control.py @@ -0,0 +1,245 @@ +import asyncio +import logging +import sys +from dataclasses import InitVar, dataclass, field +from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Union + +from flet.controls.control_event import ControlEvent +from flet.controls.control_id import ControlId +from flet.controls.keys import ScrollKey, ValueKey +from flet.controls.ref import Ref +from flet.utils.strings import random_string + +logger = logging.getLogger("flet") +controls_log = logging.getLogger("flet_controls") + +# Try importing `dataclass_transform()` for Python 3.11+, else use a no-op function +if sys.version_info >= (3, 11): # Only use it for Python 3.11+ + from typing import dataclass_transform +else: + + def dataclass_transform(): # No-op decorator for older Python versions + return lambda x: x + + +if TYPE_CHECKING: + from .page import Page + from .page_view import PageView + +__all__ = [ + "BaseControl", + "control", + "skip_field", +] + +_method_calls: dict[str, asyncio.Event] = {} +_method_call_results: dict[asyncio.Event, tuple[Any, Optional[str]]] = {} + + +def skip_field(): + return field(default=None, metadata={"skip": True}) + + +T = TypeVar("T", bound="BaseControl") + + +@dataclass_transform() +def control( + cls_or_type_name: Optional[Union[type[T], str]] = None, + *, + isolated: Optional[bool] = None, + post_init_args: int = 1, + **dataclass_kwargs, +) -> Union[type[T], Callable[[type[T]], type[T]]]: + """Decorator to optionally set 'type' and 'isolated' while behaving like @dataclass. + + - Supports `@control` (without parentheses) + - Supports `@control("custom_type")` (with optional arguments) + - Supports `@control("custom_type", post_init_args=1, isolated=True)` to + specify the number of `InitVar` arguments and isolation + """ + + # Case 1: If used as `@control` (without parentheses) + if isinstance(cls_or_type_name, type): + return _apply_control( + cls_or_type_name, None, isolated, post_init_args, **dataclass_kwargs + ) + + # Case 2: If used as `@control("custom_type", post_init_args=N, isolated=True)` + def wrapper(cls: type[T]) -> type[T]: + return _apply_control( + cls, cls_or_type_name, isolated, post_init_args, **dataclass_kwargs + ) + + return wrapper + + +def _apply_control( + cls: type[T], + type_name: Optional[str], + isolated: Optional[bool], + post_init_args: int, + **dataclass_kwargs, +) -> type[T]: + """Applies @control logic, ensuring compatibility with @dataclass.""" + cls = dataclass(**dataclass_kwargs)(cls) # Apply @dataclass first + + orig_post_init = getattr(cls, "__post_init__", lambda self, *args: None) + + def new_post_init(self: T, *args): + """Set the type and isolation only if explicitly provided.""" + if type_name is not None and (not hasattr(self, "_c") or self._c is None): + self._c = type_name # Only set type if explicitly provided + + if isolated is not None: + self._isolated = isolated # Set the _isolated field if provided + + # Pass only the correct number of arguments to `__post_init__` + orig_post_init(self, *args[:post_init_args]) + + cls.__post_init__ = new_post_init + return cls + + +@dataclass(kw_only=True) +class BaseControl: + _i: int = field(init=False, compare=False) + _c: str = field(init=False) + data: Any = skip_field() + """ + Arbitrary data of any type that can be attached to a control. + """ + + key: Union[ValueKey, ScrollKey, str, int, float, bool, None] = None + + ref: InitVar[Optional[Ref["BaseControl"]]] = None + """A reference to this control.""" + + _internals: dict = field(default_factory=dict, init=False, repr=False, compare=False) + + def __post_init__(self, ref: Optional[Ref[Any]]): + self.__class__.__hash__ = BaseControl.__hash__ + self._i = ControlId.next() + if not hasattr(self, "_c") or self._c is None: + cls_name = f"{self.__class__.__module__}.{self.__class__.__qualname__}" + raise Exception( + f"Control {cls_name} must have @control decorator with " + "type_name specified." + ) + + if ref is not None: + ref.current = self + + # control_id = self._i + # object_id = id(self) + # ctrl_type = self._c + # weakref.finalize( + # self, + # lambda: controls_log.debug( + # f"Control was garbage collected: {ctrl_type}({control_id} - {object_id})" + # ), + # ) + + def __hash__(self) -> int: + return object.__hash__(self) + + @property + def parent(self) -> Optional["BaseControl"]: + """ + The direct ancestor(parent) of this control. + + It defaults to `None` and will only have a value when this control is mounted (added to the page tree). + + The `Page` control (which is the root of the tree) is an exception - it always has `parent=None`. + """ + parent_ref = getattr(self, "_parent", None) + return parent_ref() if parent_ref else None + + @property + def page(self) -> Optional[Union["Page", "PageView"]]: + """The page (of type `Page` or `PageView`) to which this control belongs to.""" + from .page import Page, PageView + + parent = self.parent + while parent: + if isinstance(parent, (Page, PageView)): + return parent + parent = parent.parent + return None + + def is_isolated(self): + return hasattr(self, "_isolated") and self._isolated + + def init(self): + pass + + def before_update(self): + """ + `before_update()` method is called every time when the control is being updated. + Make sure not to call `update()` method within `before_update()`. + """ + pass + + def before_event(self, e: ControlEvent): + return True + + def did_mount(self): + controls_log.debug(f"{self._c}({self._i}).did_mount") + pass + + def will_unmount(self): + controls_log.debug(f"{self._c}({self._i}).will_unmount") + pass + + # public methods + def update(self) -> None: + if hasattr(self, "_frozen"): + raise Exception("Frozen control cannot be updated.") + assert self.page, ( + f"{self.__class__.__qualname__} Control must be added to the page first" + ) + self.page.update(self) + + async def _invoke_method_async( + self, + method_name: str, + arguments: Optional[dict[str, Any]] = None, + timeout: Optional[float] = 10, + ) -> Any: + assert self.page, ( + f"{self.__class__.__qualname__} Control must be added to the page first" + ) + + call_id = random_string(10) + + # register callback + evt = asyncio.Event() + _method_calls[call_id] = evt + + # call method + result = self.page.get_session().invoke_method( + self._i, call_id, method_name, arguments + ) + + try: + await asyncio.wait_for(evt.wait(), timeout=timeout) + except TimeoutError: + if call_id in _method_calls: + del _method_calls[call_id] + raise TimeoutError( + f"Timeout waiting for invokeMethod {method_name}({arguments}) call" + ) from None + + result, err = _method_call_results.pop(evt) + if err: + raise Exception(err) + return result + + def _handle_invoke_method_results( + self, call_id: str, result: Any, error: Optional[str] + ) -> None: + evt = _method_calls.pop(call_id, None) + if evt is None: + return + _method_call_results[evt] = (result, error) + evt.set() diff --git a/sdk/python/packages/flet/src/flet/controls/blur.py b/sdk/python/packages/flet/src/flet/controls/blur.py new file mode 100644 index 000000000..560aa6ad0 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/blur.py @@ -0,0 +1,45 @@ +from dataclasses import dataclass +from enum import Enum +from typing import Optional, Union + +from flet.controls.types import Number + +__all__ = [ + "Blur", + "BlurTileMode", + "BlurValue", + "OptionalBlurValue", + "OptionalBlurTileMode", +] + + +class BlurTileMode(Enum): + CLAMP = "clamp" + DECAL = "decal" + MIRROR = "mirror" + REPEATED = "repeated" + + +@dataclass +class Blur: + sigma_x: Number + """ + Horizontal sigma. + """ + + sigma_y: Number + """ + Vertical sigma. + """ + + tile_mode: Optional[BlurTileMode] = None + """ + The tile mode for the blur. + + Value is of type [`BlurTileMode`](https://flet.dev/docs/reference/types/blurtilemode). + """ + + +BlurValue = Union[Number, tuple[Number, Number], Blur] +OptionalBlurValue = Optional[BlurValue] +OptionalBlurTileMode = Optional[BlurTileMode] diff --git a/sdk/python/packages/flet/src/flet/controls/border.py b/sdk/python/packages/flet/src/flet/controls/border.py new file mode 100644 index 000000000..982132220 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/border.py @@ -0,0 +1,320 @@ +from dataclasses import dataclass, field +from enum import Enum +from typing import Optional, Union + +from flet.controls.colors import Colors +from flet.controls.types import ColorValue, Number, OptionalColorValue, OptionalNumber +from flet.utils import deprecated + +__all__ = [ + "Border", + "BorderSide", + "BorderSideStrokeAlign", + "BorderStyle", + "OptionalBorder", + "OptionalBorderSide", + "OptionalBorderSideStrokeAlign", + "BorderSideStrokeAlignValue", + "OptionalBorderSideStrokeAlignValue", + "all", + "symmetric", + "only", +] + + +class BorderSideStrokeAlign(float, Enum): + INSIDE = -1.0 + """ + The border is drawn fully inside of the border path. + """ + + CENTER = 0.0 + """ + The border is drawn on the center of the border path, with half of the + `BorderSide.width` on the inside, and the other half on the outside of + the path. + """ + + OUTSIDE = 1.0 + """ + The border is drawn on the outside of the border path. + """ + +class BorderStyle(Enum): + NONE = "none" + """Skip the border.""" + + SOLID = "solid" + """Draw the border as a solid line.""" + + +@dataclass +class BorderSide: + """ + Creates the side of a border. + + By default, the border is `1.0` logical pixels wide and solid black color. + """ + width: Number = 1.0 + """ + The width of this side of the border, in logical pixels. + + Setting width to 0.0 will result in a hairline border. This means that + the border will have the width of one physical pixel. Hairline + rendering takes shortcuts when the path overlaps a pixel more than once. + This means that it will render faster than otherwise, but it might + double-hit pixels, giving it a slightly darker/lighter result. + + To omit the border entirely, set the `style` to `BorderStyle.NONE`. + + Defaults to `1.0`. + """ + + color: ColorValue = Colors.BLACK + """ + The color of this side of the border. + + Defaults to `Colors.BLACK`. + """ + + stroke_align: "BorderSideStrokeAlignValue" = BorderSideStrokeAlign.INSIDE + """ + The relative position of the stroke on a `BorderSide` in an + `OutlinedBorder` or `Border`. + + Defaults to `BorderSideStrokeAlign.INSIDE`. + """ + + style:BorderStyle = BorderStyle.SOLID + """ + The style of this side of the border. + + To omit a side, set `style` to `BorderStyle.NONE`. + This skips painting the border, but the border still has a `width`. + + Defaults to `BorderStyle.SOLID`. + """ + + + def __post_init__(self): + assert self.width >= 0.0, "widhth must be greater than or equal to 0.0" + + # Properties + + @property + def stroke_inset(self): + """ + The amount of the stroke width that lies inside of the `BorderSide`. + + For example, this will return the `width` for a `stroke_align` of -1, half + the `width` for a `stroke_align` of 0, and 0 for a `stroke_align` of 1. + """ + return self.width * (1 - (1 + self.stroke_align) / 2) + + @property + def stroke_outset(self): + """ + The amount of the stroke width that lies outside of the [BorderSide]. + + For example, this will return 0 for a `stroke_align` of -1, half the + `width` for a `stroke_align` of 0, and the `width` for a `stroke_align` of 1. + """ + return self.width * (1 + self.stroke_align) / 2 + + @property + def stroke_offset(self): + """ + The offset of the stroke, taking into account the stroke alignment. + + For example, this will return the negative `width` of the stroke + for a `stroke_align` of -1, 0 for a `stroke_align` of 0, and the + `width` for a `stroke_align` of -1. + """ + return self.width * self.stroke_align + + # Instance Methods + + def copy_with( + self, + *, + width: OptionalNumber = None, + color: OptionalColorValue = None, + stroke_align: "OptionalBorderSideStrokeAlignValue" = None, + ) -> "BorderSide": + """ + Returns a copy of this `BorderSide` instance with the given fields replaced + with the new values. + """ + return BorderSide( + width=width if width is not None else self.width, + color=color if color is not None else self.color, + stroke_align=stroke_align + if stroke_align is not None + else self.stroke_align, + ) + + # Static Methods + + @staticmethod + def none() -> "BorderSide": + """A hairline black border that is not rendered.""" + return BorderSide(width=0.0, style=BorderStyle.NONE) + + +@dataclass +class Border: + """ + A border comprised of four sides: top, right, bottom, left. + + Each side of the border is an instance of + [`BorderSide`](https://flet.dev/docs/reference/types/borderside) class. + + Example: + ```python + container_1.border = ft.Border.all(10, ft.Colors.PINK_600) + container_1.border = ft.Border.only(bottom=ft.BorderSide(1, "black")) + ``` + """ + + top: BorderSide = field(default_factory=lambda: BorderSide.none()) + """ + Top side of the border. + + Defaults to `BorderSide.none()` + """ + + right: BorderSide = field(default_factory=lambda: BorderSide.none()) + """ + Right side of the border. + + Defaults to `BorderSide.none()` + """ + + bottom: BorderSide = field(default_factory=lambda: BorderSide.none()) + """ + Bottom side of the border. + + Defaults to `BorderSide.none()` + """ + + left: BorderSide = field(default_factory=lambda: BorderSide.none()) + """ + Left side of the border. + + Defaults to `BorderSide.none()` + """ + + # Class Methods + + @classmethod + def all( + cls, width: OptionalNumber = None, color: OptionalColorValue = None, side: "OptionalBorderSide" = None + ) -> "Border": + """ + Creates a border whose sides are all the same. + + If `side` is not `None`, it gets used and both `width` and `color` are ignored. + """ + if side is not None: + return Border(top=side, right=side, bottom=side, left=side) + bs = BorderSide(width or 1.0, color or Colors.BLACK) + return Border(left=bs, top=bs, right=bs, bottom=bs) + + @classmethod + def symmetric( + cls, + *, + vertical: BorderSide = BorderSide.none(), + horizontal: BorderSide = BorderSide.none(), + ) -> "Border": + """ + Creates a border with symmetrical vertical and horizontal sides. + + The `vertical` argument applies to the `left` and `right` sides, + and the `horizontal` argument applies to the `top` and `bottom` sides. + + All arguments default to `BorderSide.none()`. + """ + return Border(left=horizontal, top=vertical, right=horizontal, bottom=vertical) + + @classmethod + def only( + cls, + *, + left:BorderSide = BorderSide.none(), + top: BorderSide = BorderSide.none(), + right: BorderSide = BorderSide.none(), + bottom: BorderSide = BorderSide.none(), + ) -> "Border": + """Creates a `Border` from the given values.""" + return Border(left=left, top=top, right=right, bottom=bottom) + + # Instance Methods + + def copy_with( + self, + *, + left: "OptionalBorderSide" = None, + top: "OptionalBorderSide" = None, + right: "OptionalBorderSide" = None, + bottom: "OptionalBorderSide" = None, + ) -> "Border": + """ + Returns a copy of this `Border` instance with the given fields replaced + with the new values. + """ + return Border( + left=left if left is not None else self.left, + top=top if top is not None else self.top, + right=right if right is not None else self.right, + bottom=bottom if bottom is not None else self.bottom + ) + + +@deprecated( + reason="Use Border.all() instead.", + version="0.70.0", + delete_version="0.73.0", + show_parentheses=True, +) +def all(width: Optional[float] = None, color: Optional[ColorValue] = None) -> Border: + bs = BorderSide(width or 1.0, color or Colors.BLACK) + return Border(left=bs, top=bs, right=bs, bottom=bs) + + +@deprecated( + reason="Use Border.symmetric() instead.", + version="0.70.0", + delete_version="0.73.0", + show_parentheses=True, +) +def symmetric( + vertical: Optional[BorderSide] = None, horizontal: Optional[BorderSide] = None +) -> Border: + return Border(left=horizontal, top=vertical, right=horizontal, bottom=vertical) + + +@deprecated( + reason="Use Border.only() instead.", + version="0.70.0", + delete_version="0.73.0", + show_parentheses=True, +) +def only( + left: Optional[BorderSide] = None, + top: Optional[BorderSide] = None, + right: Optional[BorderSide] = None, + bottom: Optional[BorderSide] = None, +) -> Border: + return Border(left=left, top=top, right=right, bottom=bottom) + + +# Typings +OptionalBorder = Optional[Border] +""" +OptionalBorder type description +""" +OptionalBorderSide = Optional[BorderSide] +OptionalBorderSideStrokeAlign = Optional[BorderSideStrokeAlign] +BorderSideStrokeAlignValue = Union[BorderSideStrokeAlign, Number] +OptionalBorderSideStrokeAlignValue = Optional[BorderSideStrokeAlignValue] diff --git a/sdk/python/packages/flet/src/flet/controls/border_radius.py b/sdk/python/packages/flet/src/flet/controls/border_radius.py new file mode 100644 index 000000000..2c09ce119 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/border_radius.py @@ -0,0 +1,225 @@ +import dataclasses +from typing import Optional, Union + +from flet.controls.types import Number, OptionalNumber +from flet.utils import deprecated + +__all__ = [ + "BorderRadius", + "BorderRadiusValue", + "OptionalBorderRadiusValue", + "all", + "horizontal", + "vertical", + "only", +] + + +@dataclasses.dataclass +class BorderRadius: + top_left: Number + """ + Radius of the top left border corner. + """ + + top_right: Number + """ + Radius of the top right border corner. + """ + + bottom_left: Number + """ + Radius of the bottom left border corner. + """ + + bottom_right: Number + """ + Radius of the bottom right border corner. + """ + + # Class Methods + + @classmethod + def all(cls, value: Number) -> "BorderRadius": + """Creates a `BorderRadius` where all radii are `radius`.""" + return BorderRadius( + top_left=value, top_right=value, bottom_left=value, bottom_right=value + ) + + @classmethod + def horizontal(cls, *, left: Number = 0, right: Number = 0) -> "BorderRadius": + """ + Creates a horizontally symmetrical `BorderRadius` where the `left` and `right` + sides of the rectangle have the same radii. + + Both `left` and `right` default to `0`. + """ + return BorderRadius( + top_left=left, top_right=right, bottom_left=left, bottom_right=right + ) + + @classmethod + def vertical(cls, *, top: Number = 0, bottom: Number = 0) -> "BorderRadius": + """ + Creates a vertically symmetric `BorderRadius` where the `top` and `bottom` + sides of the rectangle have the same radii. + + Both `top` and `bottom` default to `0`. + """ + return BorderRadius( + top_left=top, top_right=top, bottom_left=bottom, bottom_right=bottom + ) + + @classmethod + def only( + cls, + *, + top_left: Number = 0, + top_right: Number = 0, + bottom_left: Number = 0, + bottom_right: Number = 0, + ) -> "BorderRadius": + """ + Creates a border radius with only the given values. + The other corners will be right angles. + """ + return BorderRadius( + top_left=top_left, + top_right=top_right, + bottom_left=bottom_left, + bottom_right=bottom_right, + ) + + # Instance Methods + + def copy_with( + self, + *, + top_left: OptionalNumber = None, + top_right: OptionalNumber = None, + bottom_left: OptionalNumber = None, + bottom_right: OptionalNumber = None, + ) -> "BorderRadius": + """ + Returns a copy of this `BorderRadius` instance with the given fields replaced + with the new values. + """ + return BorderRadius( + top_left=top_left if top_left is not None else self.top_left, + top_right=top_right if top_right is not None else self.top_right, + bottom_left=bottom_left if bottom_left is not None else self.bottom_left, + bottom_right=bottom_right if bottom_right is not None else self.bottom_right, + ) + + # Arithmetics + + def __add__(self, other: "BorderRadius") -> "BorderRadius": + """Adds two `BorderRadius` instances.""" + if not isinstance(other, BorderRadius): + return NotImplemented + return BorderRadius( + top_left=self.top_left + other.top_left, + top_right=self.top_right + other.top_right, + bottom_left=self.bottom_left + other.bottom_left, + bottom_right=self.bottom_right + other.bottom_right, + ) + + def __sub__(self, other: "BorderRadius") -> "BorderRadius": + """Subtracts one `BorderRadius` from another.""" + if not isinstance(other, BorderRadius): + return NotImplemented + return BorderRadius( + top_left=self.top_left - other.top_left, + top_right=self.top_right - other.top_right, + bottom_left=self.bottom_left - other.bottom_left, + bottom_right=self.bottom_right - other.bottom_right, + ) + + def __mul__(self, other: Union["BorderRadius", Number]) -> "BorderRadius": + """Multiplies `BorderRadius` by a scalar factor.""" + if isinstance(other, BorderRadius): + return BorderRadius( + top_left=self.top_left * other.top_left, + top_right=self.top_right * other.top_right, + bottom_left=self.bottom_left * other.bottom_left, + bottom_right=self.bottom_right * other.bottom_right, + ) + elif isinstance(other, Number): + return BorderRadius( + top_left=self.top_left * other, + top_right=self.top_right * other, + bottom_left=self.bottom_left * other, + bottom_right=self.bottom_right * other, + ) + return NotImplemented + + def __floordiv__(self, quotient: int) -> "BorderRadius": + """Performs floor division on `BorderRadius`.""" + if quotient == 0: + raise ZeroDivisionError("Division by zero is not possible") + return BorderRadius( + top_left=self.top_left // quotient, + top_right=self.top_right // quotient, + bottom_left=self.bottom_left // quotient, + bottom_right=self.bottom_right // quotient, + ) + + +@deprecated( + reason="Use BorderRadius.all() instead", + version="0.70.0", + delete_version="0.73.0", + show_parentheses=True, +) +def all(value: float) -> BorderRadius: + return BorderRadius( + top_left=value, top_right=value, bottom_left=value, bottom_right=value + ) + + +@deprecated( + reason="Use BorderRadius.horizontal() instead", + version="0.70.0", + delete_version="0.73.0", + show_parentheses=True, +) +def horizontal(left: float = 0, right: float = 0) -> BorderRadius: + return BorderRadius( + top_left=left, top_right=right, bottom_left=left, bottom_right=right + ) + + +@deprecated( + reason="Use BorderRadius.vertical() instead", + version="0.70.0", + delete_version="0.73.0", + show_parentheses=True, +) +def vertical(top: float = 0, bottom: float = 0) -> BorderRadius: + return BorderRadius( + top_left=top, top_right=top, bottom_left=bottom, bottom_right=bottom + ) + + +@deprecated( + reason="Use BorderRadius.only() instead", + version="0.70.0", + delete_version="0.73.0", + show_parentheses=True, +) +def only( + top_left: float = 0, + top_right: float = 0, + bottom_left: float = 0, + bottom_right: float = 0, +) -> BorderRadius: + return BorderRadius( + top_left=top_left, + top_right=top_right, + bottom_left=bottom_left, + bottom_right=bottom_right, + ) + + +BorderRadiusValue = Union[Number, BorderRadius] +OptionalBorderRadiusValue = Optional[BorderRadiusValue] diff --git a/sdk/python/packages/flet/src/flet/controls/box.py b/sdk/python/packages/flet/src/flet/controls/box.py new file mode 100644 index 000000000..3f0d67b40 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/box.py @@ -0,0 +1,388 @@ +from dataclasses import dataclass +from enum import Enum +from typing import Optional, Union + +from flet.controls.alignment import Alignment +from flet.controls.border import Border +from flet.controls.border_radius import OptionalBorderRadiusValue +from flet.controls.gradients import Gradient +from flet.controls.transform import OffsetValue +from flet.controls.types import ( + BlendMode, + ImageFit, + ImageRepeat, + Number, + OptionalBool, + OptionalColorValue, + OptionalNumber, +) + +__all__ = [ + "BoxDecoration", + "BoxShadow", + "DecorationImage", + "ColorFilter", + "FilterQuality", + "ShadowBlurStyle", + "BoxShape", + "BoxConstraints", + "BoxFit", + "ShadowValue", + "OptionalShadowValue", + "OptionalBoxDecoration", + "OptionalBoxShadow", + "OptionalDecorationImage", + "OptionalColorFilter", + "OptionalFilterQuality", + "OptionalShadowBlurStyle", + "OptionalBoxShape", + "OptionalBoxConstraints", + "OptionalBoxFit", +] + + +@dataclass +class ColorFilter: + """ + Defines a color filter that can be used with + [`Container.color_filter`](https://flet.dev/docs/controls/container#color_filter). + """ + + color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use when applying the filter. + """ + + blend_mode: Optional[BlendMode] = None + """ + The blend mode to apply to the color filter. + + Value is of type [`BlendMode`](https://flet.dev/docs/reference/types/blendmode). + """ + + +class FilterQuality(Enum): + """ + Quality levels for image sampling in Image and DecorationImage objects. + """ + + NONE = "none" + """ + The fastest filtering method, albeit also the lowest quality. + """ + + LOW = "low" + """ + Better quality than none, faster than medium. + """ + + MEDIUM = "medium" + """ + The best all around filtering method that is only worse than high at extremely + large scale factors. + """ + + HIGH = "high" + """ + Best possible quality when scaling up images by scale factors larger than 5-10x. + When images are scaled down, this can be worse than medium for scales smaller than + 0.5x, or when animating the scale factor. + This option is also the slowest. + """ + + +class ShadowBlurStyle(Enum): + NORMAL = "normal" + SOLID = "solid" + OUTER = "outer" + INNER = "inner" + + +@dataclass +class BoxShadow: + spread_radius: OptionalNumber = None + """ + The amount the box should be inflated prior to applying the blur. + + Defaults to `0.0.` + """ + + blur_radius: OptionalNumber = None + """ + The standard deviation of the Gaussian to convolve with the shadow's shape. + + Defaults to `0.0.` + """ + + color: OptionalColorValue = None + """ + [Color](https://flet.dev/docs/reference/colors) used to draw the shadow. + """ + + offset: Optional[OffsetValue] = None + """ + An instance of `Offset` class - the displacement of the shadow from the casting + element. Positive x/y offsets will shift the shadow to the right and down, while + negative offsets shift the shadow to the left and up. The offsets are relative to + the position of the element that is casting it. + + Value is of type [`Offset`](https://flet.dev/docs/reference/types/offset) and + defaults to `Offset(0,0)`. + """ + + blur_style: ShadowBlurStyle = ShadowBlurStyle.NORMAL + """ + Value is of type [`ShadowBlurStyle`](https://flet.dev/docs/reference/types/shadowblurstyle) + and defaults to `ShadowBlurStyle.NORMAL`. + """ + + +ShadowValue = Union[BoxShadow, list[BoxShadow]] +OptionalShadowValue = Union[BoxShadow, list[BoxShadow]] + + +class BoxShape(Enum): + RECTANGLE = "rectangle" + CIRCLE = "circle" + + +class BoxFit(Enum): + NONE = "none" + CONTAIN = "contain" + COVER = "cover" + FILL = "fill" + FIT_HEIGHT = "fitHeight" + FIT_WIDTH = "fitWidth" + SCALE_DOWN = "scaleDown" + + +@dataclass +class DecorationImage: + """ + An image for a box decoration. + """ + + src: Optional[str] = None + """ + The image to paint. + """ + + src_base64: Optional[str] = None + """ + The base64-encoded image to paint. + """ + + src_bytes: Optional[bytes] = None + """ + TBD + """ + + color_filter: Optional[ColorFilter] = None + """ + A color filter to apply to the image before painting it. + + Value is of type [`ColorFilter`](https://flet.dev/docs/reference/types/colorfilter). + """ + + fit: Optional[ImageFit] = None + """ + How the image should be inscribed into the box. + + Value is of type [`ImageFit`](https://flet.dev/docs/reference/types/imagefit). + """ + + alignment: Optional[Alignment] = None + """ + The alignment of the image within its bounds. + + Value is of type [`Alignment`](https://flet.dev/docs/reference/types/alignment) and + defaults to `Alignment(0.0, 0.0)`. + """ + + repeat: Optional[ImageRepeat] = None + """ + How the image should be repeated to fill the box. + + Value is of type [`ImageRepeat`](https://flet.dev/docs/reference/types/imagerepeat) + and defaults to `ImageRepeat.NO_REPEAT`. + """ + + match_text_direction: OptionalBool = None + """ + Whether to paint the image in the direction of the TextDirection. + + Value is of type `bool` and defaults to `False`. + """ + + scale: OptionalNumber = None + """ + The scale(image pixels to be shown per logical pixels) to apply to the image. + + Value is of type `float` and defaults to `1.0`. + """ + + opacity: OptionalNumber = None + """ + The opacity of the image. + + Value is of type `float` and defaults to `1.0`. + """ + + filter_quality: Optional[FilterQuality] = None + """ + The quality of the image filter. + + Value is of type [`FilterQuality`](https://flet.dev/docs/reference/types/filterquality) + and defaults to `FilterQuality.MEDIUM`. + """ + + invert_colors: OptionalBool = None + """ + Whether to invert the colors of the image while drawing. + + Value is of type `bool` and defaults to `False`. + """ + + anti_alias: OptionalBool = None + """ + Whether to paint the image in anti-aliased quality. + + Value is of type `bool` and defaults to `False`. + """ + + +@dataclass +class BoxDecoration: + """ + BoxDecoration provides a description of how to paint a box. + The box has a border, a body, and may cast a shadow. + """ + + bgcolor: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to fill in the background of + the box. + """ + + image: Optional[DecorationImage] = None + """ + An image to paint above the background `color` or `gradient`. + + Value is of type [`DecorationImage`](https://flet.dev/docs/reference/types/decorationimage). + """ + + border: Optional[Border] = None + """ + A border to draw above the background `color`, `gradient`, or `image`. + + Value is of type [`Border`](https://flet.dev/docs/reference/types/border). + """ + + border_radius: OptionalBorderRadiusValue = None + """ + The border radius of the box. + + Value is of type [`BorderRadius`](https://flet.dev/docs/reference/types/borderradius). + """ + + shadow: Optional[ShadowValue] = None + """ + A list of shadows cast by the box. + + Value is of type [`List[BoxShadow]`](https://flet.dev/docs/reference/types/boxshadow). + """ + + gradient: Optional[Gradient] = None + """ + A gradient to use when filling the box. + """ + + shape: BoxShape = BoxShape.RECTANGLE + """ + The shape to fill the `bgcolor`, `gradient`, and `image` into and to cast as the + `shadow`. + """ + + blend_mode: Optional[BlendMode] = None + """ + The blend mode to apply to the background `color` or `gradient`. + + Value is of type [`BlendMode`](https://flet.dev/docs/reference/types/blendmode). + """ + + def __post_init__(self): + assert self.blend_mode is None or self.bgcolor is not None or self.gradient is not None, "blend_mode applies to the BoxDecoration's background color or gradient, but no color or gradient was provided" + assert not (self.shape == BoxShape.CIRCLE and self.border_radius), "border_radius must be None when shape is BoxShape.CIRCLE" + + +@dataclass +class BoxConstraints: + """ + Constraints that must be respected by a size of a box. + + A Size respects a BoxConstraints if, and only if, all of the following relations + hold: + + min_width <= Size.width <= max_width + min_height <= Size.height <= max_height + + Read more about BoxConstraints [here](https://api.flutter.dev/flutter/rendering/BoxConstraints-class.html). + """ + + min_width: Number = 0 + """ + The minimum width that satisfies the constraints, such that + `0.0 <= min_width <= max_width`. + + Value is of type [`Number`](https://flet.dev/docs/reference/types/aliases#number) + and defaults to `0.0`. + """ + + min_height: Number = 0 + """ + The minimum height that satisfies the constraints, such that + `0.0 <= min_height <= max_height`. + + Value is of type [`Number`](https://flet.dev/docs/reference/types/aliases#number) + and defaults to `0.0`. + """ + + max_width: Number = float("inf") + """ + The maximum width that satisfies the constraints, such that + `min_width <= max_width <= float("inf")`. + + Value is of type [`Number`](https://flet.dev/docs/reference/types/aliases#number) + and defaults to `float("inf")` - infinity. + """ + + max_height: Number = float("inf") + """ + The maximum height that satisfies the constraints, such that + `min_height <= max_height <= float("inf")`. + + Value is of type [`Number`](https://flet.dev/docs/reference/types/aliases#number) + and defaults to `float("inf")` - infinity. + """ + + def __post_init__(self): + assert 0 <= self.min_width <= self.max_width <= float("inf"), ( + "min_width and max_width must be between 0 and infinity " + "and min_width must be less than or equal to max_width" + ) + assert 0 <= self.min_height <= self.max_height <= float("inf"), ( + "min_height and max_height must be between 0 and infinity " + "and min_height must be less than or equal to max_height" + ) + + +# typing +OptionalBoxDecoration = Optional[BoxDecoration] +OptionalBoxShadow = Optional[BoxShadow] +OptionalDecorationImage = Optional[DecorationImage] +OptionalColorFilter = Optional[ColorFilter] +OptionalFilterQuality = Optional[FilterQuality] +OptionalShadowBlurStyle = Optional[ShadowBlurStyle] +OptionalBoxShape = Optional[BoxShape] +OptionalBoxConstraints = Optional[BoxConstraints] +OptionalBoxFit = Optional[BoxFit] diff --git a/sdk/python/packages/flet/src/flet/controls/buttons.py b/sdk/python/packages/flet/src/flet/controls/buttons.py new file mode 100644 index 000000000..8dd12fd21 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/buttons.py @@ -0,0 +1,234 @@ +from dataclasses import dataclass, field +from typing import Optional + +from flet.controls.alignment import OptionalAlignment +from flet.controls.border import BorderSide, OptionalBorderSide +from flet.controls.border_radius import OptionalBorderRadiusValue +from flet.controls.control_state import ControlStateValue +from flet.controls.duration import OptionalDurationValue +from flet.controls.padding import PaddingValue +from flet.controls.text_style import TextStyle +from flet.controls.types import ( + ColorValue, + MouseCursor, + Number, + OptionalBool, + OptionalNumber, + VisualDensity, +) + +__all__ = [ + "BeveledRectangleBorder", + "ButtonStyle", + "CircleBorder", + "ContinuousRectangleBorder", + "OutlinedBorder", + "RoundedRectangleBorder", + "StadiumBorder", + "OptionalButtonStyle", + "OptionalOutlinedBorder", +] + + +@dataclass(kw_only=True) +class OutlinedBorder: + """ + An abstract class that can be used to create custom borders. + """ + + side: OptionalBorderSide = None + """ + The border outline's color and weight. + """ + + _type: Optional[str] = field(init=False, repr=False, compare=False, default=None) + + +@dataclass +class StadiumBorder(OutlinedBorder): + """ + Creates a border that looks like a stadium. + """ + + def __post_init__(self): + self._type = "stadium" + + +@dataclass +class RoundedRectangleBorder(OutlinedBorder): + """ + Creates a border with rounded rectangle corners. + """ + + radius: OptionalBorderRadiusValue = None + """ + Border radius, an instance of [`BorderRadius`](/docs/reference/types/borderradius) + or a number. + """ + + def __post_init__(self): + self._type = "roundedRectangle" + + +@dataclass +class CircleBorder(OutlinedBorder): + """ + Creates a border with a circle shape. + """ + + eccentricity: Number = 0.0 + + def __post_init__(self): + self._type = "circle" + + +@dataclass +class BeveledRectangleBorder(OutlinedBorder): + """ + Creates a border with beveled rectangle corners. + """ + + radius: OptionalBorderRadiusValue = None + """ + Border radius, an instance of [`BorderRadius`](/docs/reference/types/borderradius) + or a number. + """ + + def __post_init__(self): + self._type = "beveledRectangle" + + +@dataclass +class ContinuousRectangleBorder(OutlinedBorder): + """ + Creates a border with continuous rectangle corners. + """ + + radius: OptionalBorderRadiusValue = None + """ + Border radius, an instance of [`BorderRadius`](/docs/reference/types/borderradius) + or a number. + """ + + def __post_init__(self): + self._type = "continuousRectangle" + + +@dataclass +class ButtonStyle: + """ + Allows controlling all visual aspects of a button, such as shape, foreground, + background and shadow colors, content padding, border width and radius. + + Most of these style attributes could be configured for all or particular + [`ControlState`](https://flet.dev/docs/reference/types/controlstate) of a button, + such as `HOVERED`, `FOCUSED`, `DISABLED` and others. + """ + + color: Optional[ControlStateValue[ColorValue]] = None + """ + The color for the button's Text and Icon control descendants. + """ + + bgcolor: Optional[ControlStateValue[ColorValue]] = None + """ + The button's background fill color. + """ + + overlay_color: Optional[ControlStateValue[ColorValue]] = None + """ + The highlight color that's typically used to indicate that the button is + focused, hovered, or pressed. + """ + + shadow_color: Optional[ControlStateValue[ColorValue]] = None + """ + The shadow color of the button's Material. + """ + + surface_tint_color: Optional[ControlStateValue[ColorValue]] = None + """ + The surface tint color of the button's Material. + """ + + elevation: Optional[ControlStateValue[OptionalNumber]] = None + """ + The elevation of the button's Material. + """ + + animation_duration: OptionalDurationValue = None + """ + Defines the duration in milliseconds of animated changes for shape and + elevation. + """ + + padding: Optional[ControlStateValue[PaddingValue]] = None + """ + The padding between the button's boundary and its content. + + Value is of type [`Padding`](https://flet.dev/docs/reference/types/padding). + """ + + side: Optional[ControlStateValue[BorderSide]] = None + """ + An instance of [`BorderSide`](https://flet.dev/docs/reference/types/borderside) + class, the color and weight of the button's outline. + """ + + shape: Optional[ControlStateValue[OutlinedBorder]] = None + """ + The shape of the button's underlying Material. + + Value is of type [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder). + """ + + alignment: OptionalAlignment = None + """ + The alignment of the button's content. + + Value is of type [`Alignment`](https://flet.dev/docs/reference/types/alignment). + """ + + enable_feedback: OptionalBool = None + """ + Whether detected gestures should provide acoustic and/or haptic feedback. + + Value is of type `bool`. + """ + + text_style: Optional[ControlStateValue[TextStyle]] = None + """ + The text style of the button's `Text` control descendants. + + Value is of type [`TextStyle`](https://flet.dev/docs/reference/types/textstyle). + """ + + icon_size: Optional[ControlStateValue[OptionalNumber]] = None + """ + The icon's size inside of the button. + """ + + icon_color: Optional[ControlStateValue[ColorValue]] = None + """ + The icon's [color](https://flet.dev/docs/reference/colors) inside the button. + + If not set or `None`, then the `color` will be used. + """ + + visual_density: Optional[VisualDensity] = None + """ + Defines how compact the button's layout will be. + + Value is of type [`VisualDensity`](https://flet.dev/docs/reference/types/visualdensity). + """ + + mouse_cursor: Optional[ControlStateValue[MouseCursor]] = None + """ + The cursor to be displayed when the mouse pointer enters or is hovering + over the button. + """ + + +# Typing +OptionalButtonStyle = Optional[ButtonStyle] +OptionalOutlinedBorder = Optional[OutlinedBorder] diff --git a/sdk/python/packages/flet/src/flet/core/colors.py b/sdk/python/packages/flet/src/flet/controls/colors.py similarity index 96% rename from sdk/python/packages/flet/src/flet/core/colors.py rename to sdk/python/packages/flet/src/flet/controls/colors.py index 9d921f45b..d2dc8ca1a 100644 --- a/sdk/python/packages/flet/src/flet/core/colors.py +++ b/sdk/python/packages/flet/src/flet/controls/colors.py @@ -1,4 +1,8 @@ """ + +Code to generate colors: + +``` $lines = Get-Content "colors.dart" $section = '' @@ -33,14 +37,17 @@ } } } +``` + """ import random from enum import Enum -from typing import TYPE_CHECKING, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Optional, Union if TYPE_CHECKING: - from flet.core.types import ColorValue + from flet.controls.types import ColorValue +__all__ = ["Colors"] class Colors(str, Enum): @@ -52,15 +59,16 @@ def with_opacity(opacity: Union[int, float], color: "ColorValue") -> str: @staticmethod def random( - exclude: Optional[List["Colors"]] = None, - weights: Optional[Dict["Colors", int]] = None, + exclude: Optional[list["Colors"]] = None, + weights: Optional[dict["Colors", int]] = None, ) -> Optional["Colors"]: """ Selects a random color, with optional exclusions and weights. Args: exclude: A list of colors members to exclude from the selection. - weights: A dictionary mapping color members to their respective weights for weighted random selection. + weights: A dictionary mapping color members to their respective weights for + weighted random selection. Returns: A randomly selected color, or None if all members are excluded. @@ -93,10 +101,10 @@ def random( ON_ERROR_CONTAINER = "onerrorcontainer" OUTLINE = "outline" OUTLINE_VARIANT = "outlinevariant" - SURFACE = "surface" # previously BACKGROUND - ON_SURFACE = "onsurface" # previously ON_BACKGROUND + SURFACE = "surface" + ON_SURFACE = "onsurface" SURFACE_TINT = "surfacetint" - SURFACE_CONTAINER_HIGHEST = "surfaceContainerHighest" # previously SURFACE_VARIANT + SURFACE_CONTAINER_HIGHEST = "surfaceContainerHighest" ON_SURFACE_VARIANT = "onsurfacevariant" INVERSE_SURFACE = "inversesurface" ON_INVERSE_SURFACE = "oninversesurface" diff --git a/sdk/python/packages/flet/src/flet/controls/constrained_control.py b/sdk/python/packages/flet/src/flet/controls/constrained_control.py new file mode 100644 index 000000000..b5304b10c --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/constrained_control.py @@ -0,0 +1,370 @@ +from typing import Optional + +from flet.controls.animation import AnimationValue +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.transform import OffsetValue, RotateValue, ScaleValue +from flet.controls.types import OptionalNumber + +__all__ = ["ConstrainedControl"] + + +@control(kw_only=True) +class ConstrainedControl(Control): + width: OptionalNumber = None + """ + Imposed Control width in virtual pixels. + """ + + height: OptionalNumber = None + """ + Imposed Control height in virtual pixels. + """ + + left: OptionalNumber = None + """ + Effective inside [`Stack`](https://flet.dev/docs/controls/stack) only. The distance + that the child's left edge is inset from the left of the stack. + """ + + top: OptionalNumber = None + """ + Effective inside [`Stack`](https://flet.dev/docs/controls/stack) only. The distance + that the child's top edge is inset from the top of the stack. + """ + + right: OptionalNumber = None + """ + Effective inside [`Stack`](https://flet.dev/docs/controls/stack) only. The distance + that the child's right edge is inset from the right of the stack. + """ + + bottom: OptionalNumber = None + """ + Effective inside [`Stack`](https://flet.dev/docs/controls/stack) only. The distance + that the child's bottom edge is inset from the bottom of the stack. + """ + + rotate: Optional[RotateValue] = None + """ + Transforms control using a rotation around the center. + + The value of `rotate` property could be one of the following types: + + * `number` - a rotation in clockwise radians. Full circle `360°` is `math.pi * 2` + radians, `90°` is `pi / 2`, `45°` is `pi / 4`, etc. + * `transform.Rotate` - allows to specify rotation `angle` as well as `alignment` - + the location of rotation center. + + For example: + + ```python + ft.Image( + src="https://picsum.photos/100/100", + width=100, + height=100, + border_radius=5, + rotate=Rotate(angle=0.25 * pi, alignment=ft.Alignment.center_left()) + ) + ``` + """ + + scale: Optional[ScaleValue] = None + """ + Scale control along the 2D plane. Default scale factor is `1.0` - control is not + scaled. `0.5` - the control is twice smaller, `2.0` - the control is twice larger. + + Different scale multipliers can be specified for `x` and `y` axis, but setting + `Control.scale` property to an instance of `transform.Scale` class. + + Either `scale` or `scale_x` and `scale_y` could be specified, but not all of them, + for example: + + ```python + ft.Image( + src="https://picsum.photos/100/100", + width=100, + height=100, + border_radius=5, + scale=Scale(scale_x=2, scale_y=0.5) + ) + ``` + """ + + offset: Optional[OffsetValue] = None + """ + Applies a translation transformation before painting the control. + + The translation is expressed as a `transform.Offset` scaled to the control's size. + For example, an `Offset` with a `x` of `0.25` will result in a horizontal + translation of one quarter the width of the control. + + The following example displays container at `0, 0` top left corner of a stack as + transform applies `-1 * 100, -1 * 100` (`offset * control_size`) horizontal and + vertical translations to the control: + + ```python + import flet as ft + + def main(page: ft.Page): + + page.add( + ft.Stack( + [ + ft.Container( + bgcolor="red", + width=100, + height=100, + left=100, + top=100, + offset=ft.transform.Offset(-1, -1), + ) + ], + width=1000, + height=1000, + ) + ) + + ft.run(main) + ``` + """ + aspect_ratio: OptionalNumber = None + """ + TBD + """ + + animate_opacity: Optional[AnimationValue] = None + """ + Setting control's `animate_opacity` to either `True`, number or an instance of + `animation.Animation` class enables implicit animation of [`Control.opacity`](https://flet.dev/docs/controls#opacity) + property. + + + + ```python + import flet as ft + + def main(page: ft.Page): + + c = ft.Container( + width=150, + height=150, + bgcolor="blue", + border_radius=10, + animate_opacity=300, + ) + + def animate_opacity(e): + c.opacity = 0 if c.opacity == 1 else 1 + c.update() + + page.add( + c, + ft.ElevatedButton( + "Animate opacity", + on_click=animate_opacity, + ), + ) + + ft.app(main) + ``` + """ + + animate_size: Optional[AnimationValue] = None + """ + TBD + """ + + animate_position: Optional[AnimationValue] = None + """ + Setting control's `animate_position` to either `True`, number or an instance of + `animation.Animation` class (see above) enables implicit animation of [Control's + `left`, `top`, `right` and `bottom` properties](https://flet.dev/docs/controls#left). + + Please note Control position works inside `Stack` control only. + + + + ```python + import flet as ft + + def main(page: ft.Page): + + c1 = ft.Container(width=50, height=50, bgcolor="red", animate_position=1000) + + c2 = ft.Container( + width=50, height=50, bgcolor="green", top=60, left=0, animate_position=500 + ) + + c3 = ft.Container( + width=50, height=50, bgcolor="blue", top=120, left=0, animate_position=1000 + ) + + def animate_container(e): + c1.top = 20 + c1.left = 200 + c2.top = 100 + c2.left = 40 + c3.top = 180 + c3.left = 100 + page.update() + + page.add( + ft.Stack([c1, c2, c3], height=250), + ft.ElevatedButton("Animate!", on_click=animate_container), + ) + + ft.run(main) + ``` + """ + + animate_rotation: Optional[AnimationValue] = None + """ + Setting control's `animate_rotation` to either `True`, number or an instance of + `animation.Animation` class enables implicit animation of [`Control.rotate`](https://flet.dev/docs/controls#rotate) + property. + + + + ```python + from math import pi + import flet as ft + + def main(page: ft.Page): + + c = ft.Container( + width=100, + height=70, + bgcolor="blue", + border_radius=5, + rotate=ft.transform.Rotate(0, alignment=ft.alignment.center), + animate_rotation=ft.animation.Animation(300, ft.AnimationCurve.BOUNCE_OUT), + ) + + def animate(e): + c.rotate.angle += pi / 2 + page.update() + + page.vertical_alignment = ft.MainAxisAlignment.CENTER + page.horizontal_alignment = ft.CrossAxisAlignment.CENTER + page.spacing = 30 + page.add( + c, + ft.ElevatedButton("Animate!", on_click=animate), + ) + + ft.run(main) + ``` + """ + + animate_scale: Optional[AnimationValue] = None + """ + Setting control's `animate_scale` to either `True`, number or an instance of + `animation.Animation` class enables implicit animation of [`Control.scale`](https://flet.dev/docs/controls#scale) + property. + + + + ```python + import flet as ft + + def main(page: ft.Page): + + c = ft.Container( + width=100, + height=100, + bgcolor="blue", + border_radius=5, + scale=ft.transform.Scale(scale=1), + animate_scale=ft.animation.Animation(600, ft.AnimationCurve.BOUNCE_OUT), + ) + + def animate(e): + c.scale = 2 + page.update() + + page.vertical_alignment = ft.MainAxisAlignment.CENTER + page.horizontal_alignment = ft.CrossAxisAlignment.CENTER + page.spacing = 30 + page.add( + c, + ft.ElevatedButton("Animate!", on_click=animate), + ) + + ft.run(main) + ``` + """ + + animate_offset: Optional[AnimationValue] = None + """ + Setting control's `animate_offset` to either `True`, number or an instance of + `animation.Animation` class enables implicit animation of `Control.offset` property. + + `offset` property is an instance of `transform.Offset` class which specifies + horizontal `x` and vertical `y` offset of a control scaled to control's size. + For example, an offset `transform.Offset(-0.25, 0)` will result in a horizontal + translation of one quarter the width of the control. + + Offset animation is used for various sliding effects: + + + + ```python + import flet as ft + + def main(page: ft.Page): + + c = ft.Container( + width=150, + height=150, + bgcolor="blue", + border_radius=10, + offset=ft.transform.Offset(-2, 0), + animate_offset=ft.animation.Animation(1000), + ) + + def animate(e): + c.offset = ft.transform.Offset(0, 0) + c.update() + + page.add( + c, + ft.ElevatedButton("Reveal!", on_click=animate), + ) + + ft.run(main) + ``` + """ + + on_animation_end: OptionalControlEventHandler["ConstrainedControl"] = None + """ + All controls with `animate_*` properties have `on_animation_end` event handler + which is called when animation complete and can be used to chain multiple + animations. + + Event's object `data` field contains the name of animation: + + * `opacity` + * `rotation` + * `scale` + * `offset` + * `position` + * `container` + + For example: + + ```python + c = ft.Container( + ft.Text("Animate me!"), + # ... + animate=ft.animation.Animation(1000, "bounceOut"), + on_animation_end=lambda e: print("Container animation end:", e.data) + ) + ``` + """ diff --git a/sdk/python/packages/flet/src/flet/controls/control.py b/sdk/python/packages/flet/src/flet/controls/control.py new file mode 100644 index 000000000..f8146e00a --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/control.py @@ -0,0 +1,259 @@ +from dataclasses import dataclass +from typing import Optional, Union + +from flet.controls.base_control import BaseControl +from flet.controls.material.badge import BadgeValue +from flet.controls.material.tooltip import TooltipValue +from flet.controls.types import Number, ResponsiveNumber + +__all__ = ["Control", "OptionalControl"] + + +@dataclass(kw_only=True) +class Control(BaseControl): + """ + TBD + """ + + expand: Optional[Union[bool, int]] = None + """ + When a child Control is placed into a [`Column`](https://flet.dev/docs/controls/column) + or a [`Row`](https://flet.dev/docs/controls/row) you can "expand" it to fill the + available space. + `expand` property could be a boolean value (`True` - expand control to fill all + available space) or an integer - an "expand factor" specifying how to divide a free + space with other expanded child controls. + + For more information and examples about `expand` property see "Expanding children" + sections in [`Column`](https://flet.dev/docs/controls/column#expanding-children) or + [`Row`](https://flet.dev/docs/controls/row#expanding-children). + + Here is an example of expand being used in action for both [`Column`](https://flet.dev/docs/controls/column) + and [`Row`](https://flet.dev/docs/controls/row): + + ```python + import flet as ft + + def main(page: ft.Page): + page.spacing = 0 + page.padding = 0 + page.add( + ft.Column( + controls=[ + ft.Row( + [ + ft.Card( + content=ft.Text("Card_1"), + color=ft.Colors.ORANGE_300, + expand=True, + height=page.height, + margin=0, + ), + ft.Card( + content=ft.Text("Card_2"), + color=ft.Colors.GREEN_100, + expand=True, + height=page.height, + margin=0, + ), + ], + expand=True, + spacing=0, + ), + ], + expand=True, + spacing=0, + ), + ) + + ft.app(main) + ``` + """ + + expand_loose: Optional[bool] = None + """ + Effective only if `expand` is `True`. + + If `expand_loose` is `True`, the child control of a + [`Column`](https://flet.dev/docs/controls/column) or a [`Row`](https://flet.dev/docs/controls/row) + will be given the flexibility to expand to fill the available space in the main + axis (e.g., horizontally for a Row or vertically for a Column), but will not be + required to fill the available space. + + The default value is `False`. + + Here is the example of Containers placed in Rows with `expand_loose = True`: + ```python + import flet as ft + + + class Message(ft.Container): + def __init__(self, author, body): + super().__init__() + self.content = ft.Column( + controls=[ + ft.Text(author, weight=ft.FontWeight.BOLD), + ft.Text(body), + ], + ) + self.border = ft.border.all(1, ft.Colors.BLACK) + self.border_radius = ft.border_radius.all(10) + self.bgcolor = ft.Colors.GREEN_200 + self.padding = 10 + self.expand = True + self.expand_loose = True + + + def main(page: ft.Page): + chat = ft.ListView( + padding=10, + spacing=10, + controls=[ + ft.Row( + alignment=ft.MainAxisAlignment.START, + controls=[ + Message( + author="John", + body="Hi, how are you?", + ), + ], + ), + ft.Row( + alignment=ft.MainAxisAlignment.END, + controls=[ + Message( + author="Jake", + body="Hi I am good thanks, how about you?", + ), + ], + ), + ft.Row( + alignment=ft.MainAxisAlignment.START, + controls=[ + Message( + author="John", + body="Lorem Ipsum is simply dummy text of the printing and + typesetting industry. Lorem Ipsum has been the industry's + standard dummy text ever since the 1500s, when an unknown + printer took a galley of type and scrambled it to make a + type specimen book.", + ), + ], + ), + ft.Row( + alignment=ft.MainAxisAlignment.END, + controls=[ + Message( + author="Jake", + body="Thank you!", + ), + ], + ), + ], + ) + + page.window.width = 393 + page.window.height = 600 + page.window.always_on_top = False + + page.add(chat) + + + ft.run(main) + + ``` + + + """ + + col: ResponsiveNumber = 12 # todo: if dict, validate keys with those in parent (ResponsiveRow.breakpoints) + """ + If a parent of the control is ResponsiveRow, `col` property is used to determine + how many virtual columns of a screen the control will span. + + Can be a number or a dictionary configured to have a different value for specific + breakpoints, for example `col={"sm": 6}`. Breakpoints are named dimension ranges: + + | Breakpoint | Dimension | + |---|---| + | xs | <576px | + | sm | ≥576px | + | md | ≥768px | + | lg | ≥992px | + | xl | ≥1200px | + | xxl | ≥1400px | + + If `col` property is not specified, it spans the maximum number of columns (12). + """ + + opacity: Number = 1.0 + """ + Defines the transparency of the control. + + Value ranges from `0.0` (completely transparent) to `1.0` (completely opaque + without any transparency) and defaults to `1.0`. + """ + + tooltip: Optional[TooltipValue] = None + """ + The `tooltip` property supports both strings + and [`Tooltip`](https://flet.dev/docs/reference/types/tooltip.md) objects. + """ + + badge: Optional[BadgeValue] = None + """ + The `badge` property supports both strings and + [`Badge`](https://flet.dev/docs/reference/types/badge.md) objects. + """ + + visible: bool = True + """ + Every control has `visible` property which is `True` by default - control is + rendered on the page. Setting `visible` to `False` completely prevents control (and + all its children if any) from rendering on a page canvas. Hidden controls cannot be + focused or selected with a keyboard or mouse and they do not emit any events. + """ + + disabled: bool = False + """ + Every control has `disabled` property which is `False` by default - control and all + its children are enabled. + `disabled` property is mostly used with data entry controls like `TextField`, + `Dropdown`, `Checkbox`, buttons. + However, `disabled` could be set to a parent control and its value will be + propagated down to all children recursively. + + For example, if you have a form with multiple entry controls you can disable them + all together by disabling container: + + ```python + c = ft.Column(controls=[ + ft.TextField(), + ft.TextField() + ]) + c.disabled = True + page.add(c) + ``` + """ + + rtl: bool = False + """ + `True` to set text direction to right-to-left. + """ + + def before_update(self): + super().before_update() + assert 0.0 <= self.opacity <= 1.0, ( + "opacity must be between 0.0 and 1.0 inclusive" + ) + assert self.expand is None or isinstance(self.expand, (bool, int)), ( + "expand must be of bool or int type" + ) + + def clean(self) -> None: + raise Exception("Deprecated!") + + +# Typing +OptionalControl = Optional[Control] diff --git a/sdk/python/packages/flet/src/flet/controls/control_builder.py b/sdk/python/packages/flet/src/flet/controls/control_builder.py new file mode 100644 index 000000000..39246c040 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/control_builder.py @@ -0,0 +1,64 @@ +import weakref +from dataclasses import InitVar +from typing import Any, Callable, ClassVar, Generic, Optional, TypeVar + +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.ref import Ref + +__all__ = ["ControlBuilder"] + +T = TypeVar("T") + + +@control("ControlBuilder", post_init_args=4) +class ControlBuilder(Control, Generic[T]): + """ + Builds control tree on every update based on data. + + ----- + + Online docs: https://flet.dev/docs/controls/controlbuilder + """ + + state: InitVar[T] + builder: InitVar[Callable[[T], Control]] + state_key: InitVar[Optional[Callable[[T], Any]]] = None + content: Optional[Control] = None + + # Cache: (control_id, state_id) -> control + _builder_cache: ClassVar[weakref.WeakValueDictionary[tuple[int, Any], Control]] = ( + weakref.WeakValueDictionary() + ) + + def __post_init__( + self, + ref: Optional[Ref[Any]], + state: T, + builder: Callable[[T], Control], + state_key: Optional[Callable[[T], Any]] = None, + ): + Control.__post_init__(self, ref) + self._state: T = state + self._builder = builder + self._state_key = state_key + + def before_update(self): + # print(f"ControlBuilder({self._i}).before_update") + frozen = getattr(self, "_frozen", None) + if frozen: + del self._frozen + + cache_key = (self._i, self._state_key(self._state)) if self._state_key else None + + if cache_key is not None and cache_key in self._builder_cache: + self.content = self._builder_cache[cache_key] + else: + self.content = self._builder(self._state) + if cache_key is not None and self.content: + self._builder_cache[cache_key] = self.content + + if self.content: + object.__setattr__(self.content, "_frozen", True) + if frozen: + self._frozen = frozen diff --git a/sdk/python/packages/flet/src/flet/controls/control_event.py b/sdk/python/packages/flet/src/flet/controls/control_event.py new file mode 100644 index 000000000..5d62a9486 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/control_event.py @@ -0,0 +1,105 @@ +import inspect +import sys +from dataclasses import InitVar, dataclass, field +from typing import ( + TYPE_CHECKING, + Any, + Callable, + ForwardRef, + Generic, + Optional, + TypeVar, + Union, + _eval_type, + get_args, + get_origin, +) + +if TYPE_CHECKING: + from .base_control import BaseControl + from .page import Page + from .page_view import PageView + +__all__ = [ + "ControlEvent", + "OptionalControlEventHandler", + "Event", + "ControlEventHandler", + "EventHandler", + "OptionalEventHandler", + "EventControlType", +] + +EventControlType = TypeVar("EventControlType", bound="BaseControl") + + +@dataclass +class Event(Generic[EventControlType]): + name: str + data: Optional[Any] = field(default=None, kw_only=True) + control: EventControlType = field(repr=False) + + @property + def page(self) -> Optional[Union["Page", "PageView"]]: + return self.control.page + + @property + def target(self) -> int: + return self.control._i + + @staticmethod + def get_event_field_type(control: Any, field_name: str): + frame = inspect.currentframe().f_back + globalns = sys.modules[control.__class__.__module__].__dict__ + localns = frame.f_globals.copy() + localns.update(frame.f_locals) + + merged_annotations = {} + + for cls in control.__class__.__mro__: + annotations = getattr(cls, "__annotations__", {}) + for name, annotation in annotations.items(): + if get_origin(annotation) is InitVar or str(annotation).startswith( + "dataclasses.InitVar" + ): + continue # Skip InitVar + if name not in merged_annotations: + merged_annotations[name] = annotation + + if field_name not in merged_annotations: + return None + + annotation = merged_annotations[field_name] + + try: + # Resolve forward refs manually + if isinstance(annotation, ForwardRef): + annotation = _eval_type(annotation, globalns, localns) + + clbs = get_args(annotation) # callable(s) + clb = clbs[1] if len(clbs) > 2 else clbs[0] + event_type = get_args(clb)[0][0] + + if isinstance(event_type, ForwardRef): + event_type = _eval_type(event_type, globalns, localns) + + return event_type + except Exception as e: + raise Exception(f"[resolve error] {field_name}: {e}") from e + + +EventType = TypeVar("EventType", bound=Event) + +ControlEventHandler = Union[ + Callable[[], Any], Callable[[Event[EventControlType]], Any] +] + +OptionalControlEventHandler = Union[ + Callable[[], Any], Callable[[Event[EventControlType]], Any], None, +] + +EventHandler = Union[Callable[[], Any], Callable[[EventType], Any]] + +OptionalEventHandler = Union[Callable[[], Any], Callable[[EventType], Any], None] + +ControlEvent = Event["BaseControl"] diff --git a/sdk/python/packages/flet/src/flet/controls/control_id.py b/sdk/python/packages/flet/src/flet/controls/control_id.py new file mode 100644 index 000000000..9e74000c1 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/control_id.py @@ -0,0 +1,22 @@ +import itertools +import threading + +from flet.utils.locks import NopeLock +from flet.utils.platform_utils import is_pyodide + + +class ControlId: + """ + Generates unique, auto-incrementing integers safely across + multiple threads and asyncio tasks using itertools.count. + """ + + _counter_iterator = itertools.count(3) # Creates an iterator starting at 3 + _lock = threading.Lock() if not is_pyodide() else NopeLock() + + @classmethod + def next(cls) -> int: + """Returns the next unique integer identifier.""" + with cls._lock: + # next() on the iterator is atomic *relative to the lock* + return next(cls._counter_iterator) diff --git a/sdk/python/packages/flet/src/flet/controls/control_state.py b/sdk/python/packages/flet/src/flet/controls/control_state.py new file mode 100644 index 000000000..27cd52d04 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/control_state.py @@ -0,0 +1,20 @@ +from enum import Enum +from typing import TypeVar, Union + +__all__ = ["ControlState", "ControlStateValue"] + + +class ControlState(Enum): + HOVERED = "hovered" + FOCUSED = "focused" + PRESSED = "pressed" + DRAGGED = "dragged" + SELECTED = "selected" + SCROLLED_UNDER = "scrolledUnder" + DISABLED = "disabled" + ERROR = "error" + DEFAULT = "default" + + +T = TypeVar("T") +ControlStateValue = Union[T, dict[ControlState, T]] diff --git a/sdk/python/packages/flet/src/flet/controls/core/__init__.py b/sdk/python/packages/flet/src/flet/controls/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sdk/python/packages/flet/src/flet/controls/core/alert_dialog.py b/sdk/python/packages/flet/src/flet/controls/core/alert_dialog.py new file mode 100644 index 000000000..b320c6704 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/alert_dialog.py @@ -0,0 +1,249 @@ +from dataclasses import field +from typing import Optional + +from flet.controls.alignment import Alignment +from flet.controls.base_control import control +from flet.controls.buttons import OutlinedBorder +from flet.controls.control import Control +from flet.controls.dialog_control import DialogControl +from flet.controls.padding import OptionalPaddingValue +from flet.controls.text_style import OptionalTextStyle +from flet.controls.types import ( + ClipBehavior, + IconValueOrControl, + MainAxisAlignment, + OptionalColorValue, + OptionalNumber, + OptionalString, + StrOrControl, +) + +__all__ = ["AlertDialog"] + + +@control("AlertDialog") +class AlertDialog(DialogControl): + """ + An alert dialog informs the user about situations that require acknowledgement. + An alert dialog has an optional title and an optional list of actions. The title + is displayed above the content and the actions are displayed below the content. + + Online docs: https://flet.dev/docs/controls/alertdialog + """ + + content: Optional[Control] = None + """ + The (optional) content of the dialog is displayed in the center of the dialog in + a lighter font. Typically this is a [`Column`](https://flet.dev/docs/controls/column) + that contains the dialog's [`Text`](https://flet.dev/docs/controls/text) message. + + Value is of type `Control`. + """ + + modal: bool = False + """ + Whether dialog can be dismissed/closed by clicking the area outside of it. + + Value is of type `bool` and defaults to `False`. + """ + + title: Optional[StrOrControl] = None + """ + The (optional) title of the dialog is displayed in a large font at the top of the + dialog. + + Typically a [`Text`](https://flet.dev/docs/controls/text) control. + """ + + actions: list[Control] = field(default_factory=list) + """ + The (optional) set of actions that are displayed at the bottom of the dialog. + + Typically this is a list of [`TextButton`](https://flet.dev/docs/controls/textbutton) + controls. + """ + + bgcolor: OptionalColorValue = None + """ + The background [color](https://flet.dev/docs/reference/colors) of the dialog's + surface. + """ + + elevation: OptionalNumber = None + """ + Defines the elevation (z-coordinate) at which the dialog should appear. + + Value is of type [`OptionalNumber`](https://flet.dev/docs/reference/types/aliases#optionalnumber). + """ + + icon: Optional[IconValueOrControl] = None + """ + A control that is displayed at the top of the dialog. Typically a + [`Icon`](https://flet.dev/docs/controls/icon) control. + """ + + title_padding: OptionalPaddingValue = None + """ + Padding around the title. + + If there is no title, no padding will be provided. Otherwise, this padding is used. + + Value is of type + [`PaddingValue`](https://flet.dev/docs/reference/types/aliases#paddingvalue). + + Defaults to providing `24` pixels on the top, left, and right of the title. + If the `content` is not `None`, then no bottom padding is provided (but see + [`content_padding`](https://flet.dev/docs/controls/alertdialog#content_padding)). + If it is not set, then an extra `20` pixels of bottom padding is added to separate + the title from the actions. + """ + + content_padding: OptionalPaddingValue = None + """ + Padding around the content. + + If there is no content, no padding will be provided. Otherwise, padding of 20 pixels + is provided above the content to separate the content from the title, and padding of + 24 pixels is provided on the left, right, and bottom to separate the content from + the other edges of the dialog. + + Value is of type + [`PaddingValue`](https://flet.dev/docs/reference/types/aliases#paddingvalue). + """ + + actions_padding: OptionalPaddingValue = None + """ + Padding around the set of actions at the bottom of the dialog. + + Typically used to provide padding to the button bar between the button bar and the + edges of the dialog. + + If are no actions, then no padding will be included. The padding around the button + bar defaults to zero. + + Value is of type + [`PaddingValue`](https://flet.dev/docs/reference/types/aliases#paddingvalue). + """ + + actions_alignment: Optional[MainAxisAlignment] = None + """ + Defines the horizontal layout of the actions. + + Value is of type + [`MainAxisAlignment`](https://flet.dev/docs/reference/types/mainaxisalignment) + and defaults to `MainAxisAlignment.END`. + """ + + shape: Optional[OutlinedBorder] = None + """ + The shape of the dialog. + + Value is of type + [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder) + and defaults to `RoundedRectangleBorder(radius=4.0)`. + """ + + inset_padding: OptionalPaddingValue = None + """ + Padding around the Dialog itself. + + Value is of type + [`PaddingValue`](https://flet.dev/docs/reference/types/aliases#paddingvalue). + + Defaults to `padding.symmetric(vertical=40, horizontal=24)` - 40 pixels horizontally + and 24 pixels vertically outside of the dialog box. + """ + + icon_padding: OptionalPaddingValue = None + """ + Padding around the `icon`. + + Value is of type + [`PaddingValue`](https://flet.dev/docs/reference/types/aliases#paddingvalue). + """ + + action_button_padding: OptionalPaddingValue = None + """ + The padding that surrounds each button in `actions`. + + Value is of type [`PaddingValue`](https://flet.dev/docs/reference/types/aliases#paddingvalue). + """ + + surface_tint_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used as a surface tint overlay + on the dialog's background color, which reflects the dialog's elevation. + """ + + shadow_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used to paint a drop shadow + under the dialog, which reflects the dialog's elevation. + """ + + icon_color: OptionalColorValue = None + """ + TBD + """ + + scrollable: bool = False + """ + TBD + """ + + actions_overflow_button_spacing: OptionalNumber = None + """ + TBD + """ + + alignment: Optional[Alignment] = None + """ + TBD + """ + + content_text_style: OptionalTextStyle = None + """ + TBD + """ + + title_text_style: OptionalTextStyle = None + """ + TBD + """ + + clip_behavior: Optional[ClipBehavior] = None + """ + Controls how the contents of the dialog are clipped (or not) to the given `shape`. + + Value is of type + [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) + and defaults to `ClipBehavior.NONE`. + """ + + semantics_label: OptionalString = None + """ + The semantic label of the dialog used by accessibility frameworks to announce screen + transitions when the dialog is opened and closed. + + In iOS, if this label is not provided, a semantic label will be inferred from the + `title` if it is not null. + + Value is of type `str`. + """ + + barrier_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the modal barrier that + darkens everything below the dialog. + + If `None`, the + [`DialogTheme.barrier_color`](https://flet.dev/docs/reference/types/dialogtheme#barrier_color) + is used. If it is also `None`, then `Colors.BLACK_54` is used. + """ + + def before_update(self): + super().before_update() + assert ( + self.title or self.content or self.actions + ), "AlertDialog has nothing to display. Provide at minimum one of the " + "following: title, content, actions" diff --git a/sdk/python/packages/flet/src/flet/controls/core/animated_switcher.py b/sdk/python/packages/flet/src/flet/controls/core/animated_switcher.py new file mode 100644 index 000000000..c5ef4d92a --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/animated_switcher.py @@ -0,0 +1,82 @@ +from dataclasses import field +from enum import Enum + +from flet.controls.animation import AnimationCurve +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.duration import Duration, DurationValue + +__all__ = ["AnimatedSwitcher", "AnimatedSwitcherTransition"] + + +class AnimatedSwitcherTransition(Enum): + FADE = "fade" + ROTATION = "rotation" + SCALE = "scale" + + +@control("AnimatedSwitcher") +class AnimatedSwitcher(ConstrainedControl): + """ + A control that by default does a cross-fade between a new control and the control + previously set on the AnimatedSwitcher as a `content`. + + Online docs: https://flet.dev/docs/controls/animatedswitcher + """ + + content: Control + """ + The content to display. When the `content` changes, the AnimatedSwitcher will + animate the transition from the old `content` to the new one. + + Value is of type `Control`. + """ + + duration: DurationValue = field(default_factory=lambda: Duration(seconds=1)) + """ + The duration, in milliseconds, of the transition from the old `content` value + to the new one. + + Value is of type `int` defaults to `1000` milliseconds. + """ + + reverse_duration: DurationValue = field(default_factory=lambda: Duration(seconds=1)) + """ + The duration, in milliseconds, of the transition from the new `content` value + to the old one. + + Value is of type `int` and defaults to `1000` milliseconds. + """ + + switch_in_curve: AnimationCurve = AnimationCurve.LINEAR + """ + The animation curve to use when transitioning in a new `content`. + + Value is of type + [`AnimationCurve`](https://flet.dev/docs/reference/types/animationcurve) and + defaults to [`AnimationCurve.LINEAR`](https://flet.dev/docs/reference/types/animationcurve). + """ + + switch_out_curve: AnimationCurve = AnimationCurve.LINEAR + """ + The animation curve to use when transitioning a previous `content` out. + + Value is of type + [`AnimationCurve`](https://flet.dev/docs/reference/types/animationcurve) and + defaults to [`AnimationCurve.LINEAR`](https://flet.dev/docs/reference/types/animationcurve). + """ + + transition: AnimatedSwitcherTransition = AnimatedSwitcherTransition.FADE + """ + An animation type to transition between new and old `content`. + + Value is of type + [`AnimatedSwitcherTransition`](https://flet.dev/docs/reference/types/animatedswitchertransition) + and defaults to + [`AnimatedSwitcherTransition.FADE`](https://flet.dev/docs/reference/types/animatedswitchertransition). + """ + + def before_update(self): + super().before_update() + assert self.content.visible, "content must be visible" diff --git a/sdk/python/packages/flet/src/flet/core/autofill_group.py b/sdk/python/packages/flet/src/flet/controls/core/autofill_group.py similarity index 70% rename from sdk/python/packages/flet/src/flet/core/autofill_group.py rename to sdk/python/packages/flet/src/flet/controls/core/autofill_group.py index c0da15506..bc914fdc2 100644 --- a/sdk/python/packages/flet/src/flet/core/autofill_group.py +++ b/sdk/python/packages/flet/src/flet/controls/core/autofill_group.py @@ -1,8 +1,10 @@ from enum import Enum -from typing import Any, Optional +from typing import Optional -from flet.core.control import Control -from flet.core.ref import Ref +from flet.controls.base_control import control +from flet.controls.control import Control + +__all__ = ["AutofillGroup", "AutofillHint", "AutofillGroupDisposeAction"] class AutofillHint(Enum): @@ -79,61 +81,31 @@ class AutofillGroupDisposeAction(Enum): CANCEL = "cancel" +@control("AutofillGroup") class AutofillGroup(Control): """ This control is used to group autofill controls together. - ----- - Online docs: https://flet.dev/docs/controls/autofillgroup """ - def __init__( - self, - content: Control = None, - dispose_action: Optional[AutofillGroupDisposeAction] = None, - # - # Control - # - ref: Optional[Ref] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - data=data, - ) + content: Control + """ + The content control of this group. - self.content = content - self.dispose_action = dispose_action + Value is of type `Control`. + """ - def _get_control_name(self): - return "autofillgroup" + dispose_action: Optional[AutofillGroupDisposeAction] = None + """ + The action to be run when this `AutofillGroup` is the topmost `AutofillGroup` + and it's being disposed, in order to clean up the current autofill context. - def _get_children(self): - self.__content._set_attr_internal("n", "content") - return [self.__content] + Value is of type + [`AutofillGroupDisposeAction`](https://flet.dev/docs/reference/types/autofillgroupdisposeaction) + and defaults to `AutofillGroupDisposeAction.COMMIT`. + """ def before_update(self): super().before_update() - assert self.__content.visible, "content must be visible" - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # dispose_action - @property - def dispose_action(self) -> Optional[AutofillGroupDisposeAction]: - return self.__dispose_action - - @dispose_action.setter - def dispose_action(self, value: Optional[AutofillGroupDisposeAction]): - self.__dispose_action = value - self._set_enum_attr("disposeAction", value, AutofillGroupDisposeAction) + assert self.content.visible, "content must be visible" diff --git a/sdk/python/packages/flet/src/flet/controls/core/canvas/arc.py b/sdk/python/packages/flet/src/flet/controls/core/canvas/arc.py new file mode 100644 index 000000000..f89274820 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/canvas/arc.py @@ -0,0 +1,65 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.core.canvas.shape import Shape +from flet.controls.painting import Paint +from flet.controls.types import OptionalNumber + + +@control("Arc") +class Arc(Shape): + """ + Draws an arc scaled to fit inside the given rectangle. + + It starts from `start_angle` radians around the oval up to `start_angle` + + `sweep_angle` radians around the oval, with zero radians being the point on + the right hand side of the oval that crosses the horizontal line that + intersects the center of the rectangle and with positive angles going + clockwise around the oval. If `use_center` is `True`, the arc is closed back + to the center, forming a circle sector. Otherwise, the arc is not closed, + forming a circle segment. + + https://api.flutter.dev/flutter/dart-ui/Canvas/drawArc.html + """ + + x: OptionalNumber = None + """ + The x-axis coordinate of the arc's top left point. + """ + + y: OptionalNumber = None + """ + The y-axis coordinate of the arc's top left point. + """ + + width: OptionalNumber = None + """ + Width of the rectangle containing the arc's oval. + """ + + height: OptionalNumber = None + """ + Height of the rectangle containing the arc's oval. + """ + + start_angle: OptionalNumber = None + """ + Starting angle in radians to draw arc from. + """ + + sweep_angle: OptionalNumber = None + """ + The length of the arc in radians. + """ + + use_center: Optional[bool] = None + """ + If `use_center` is `True`, the arc is closed back to the center, forming a + circle sector. Otherwise, the arc is not closed, forming a circle segment. + """ + + paint: Optional[Paint] = None + """ + A style to draw an arc with. The value of this property is the instance of + [`Paint`](https://flet.dev/docs/reference/types/paint) class. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/core/canvas/canvas.py b/sdk/python/packages/flet/src/flet/controls/core/canvas/canvas.py new file mode 100644 index 000000000..5f6b1054c --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/canvas/canvas.py @@ -0,0 +1,55 @@ +from dataclasses import dataclass, field +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import Event, OptionalEventHandler +from flet.controls.core.canvas.shape import Shape +from flet.controls.types import OptionalNumber + + +@dataclass +class CanvasResizeEvent(Event["Canvas"]): + width: float = field(metadata={"data_field": "w"}) + """ + New width of the canvas. + """ + + height: float = field(metadata={"data_field": "h"}) + """ + New height of the canvas. + """ + + +@control("Canvas") +class Canvas(ConstrainedControl): + """ + Canvas is a control for drawing arbitrary graphics using a set of primitives or + "shapes" such as line, arc, path and text. + """ + + shapes: list[Shape] = field(default_factory=list) + """ + The list of `Shape` objects (see below) to draw on the canvas. + """ + + content: Optional[Control] = None + """ + TBD + """ + + resize_interval: OptionalNumber = None + """ + Sampling interval in milliseconds for `on_resize` event. + + Defaults to `0` - call `on_resize` immediately on every change. + """ + + on_resize: OptionalEventHandler[CanvasResizeEvent] = None + """ + Fires when the size of canvas has changed. + + Event object `e` is an instance of + [CanvasResizeEvent](https://flet.dev/docs/reference/types/canvasresizeevent). + """ diff --git a/sdk/python/packages/flet/src/flet/controls/core/canvas/circle.py b/sdk/python/packages/flet/src/flet/controls/core/canvas/circle.py new file mode 100644 index 000000000..e3d06ed72 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/canvas/circle.py @@ -0,0 +1,35 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.core.canvas.shape import Shape +from flet.controls.painting import Paint +from flet.controls.types import OptionalNumber + + +@control("Circle") +class Circle(Shape): + """ + Draws a circle. + """ + + x: OptionalNumber = None + """ + The x-axis coordinate of the circle's center point. + """ + + y: OptionalNumber = None + """ + The y-axis coordinate of the circle's center point. + """ + + radius: OptionalNumber = None + """ + Circle's radius. + """ + + paint: Optional[Paint] = None + """ + A style to draw a circle with. The value of this property is the instance of + [`Paint`](https://flet.dev/docs/reference/types/paint) class. + """ + diff --git a/sdk/python/packages/flet/src/flet/controls/core/canvas/color.py b/sdk/python/packages/flet/src/flet/controls/core/canvas/color.py new file mode 100644 index 000000000..e5cf76504 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/canvas/color.py @@ -0,0 +1,26 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.core.canvas.shape import Shape +from flet.controls.types import BlendMode, OptionalColorValue + + +@control("Color") +class Color(Shape): + """ + Paints the given `color` onto the canvas, applying the given `blend_mode`, with + the given color being the source and the background being the destination. + """ + + color: OptionalColorValue = None + """ + [Color](https://flet.dev/docs/reference/colors) to paint onto the canvas. + """ + + blend_mode: Optional[BlendMode] = None + """ + Blend mode to apply. + + Value is of type [`BlendMode`](https://flet.dev/docs/reference/types/blendmode). + """ + diff --git a/sdk/python/packages/flet/src/flet/controls/core/canvas/fill.py b/sdk/python/packages/flet/src/flet/controls/core/canvas/fill.py new file mode 100644 index 000000000..28a2ba3dc --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/canvas/fill.py @@ -0,0 +1,22 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.core.canvas.shape import Shape +from flet.controls.painting import Paint + + +@control("Fill") +class Fill(Shape): + """ + Fills the canvas with the given `Paint`. + + To fill the canvas with a solid color and blend mode, consider `Color` shape + instead. + """ + + paint: Optional[Paint] = None + """ + A style to fill the canvas with. The value of this property is the instance of + [`Paint`](https://flet.dev/docs/reference/types/paint) class. + """ + diff --git a/sdk/python/packages/flet/src/flet/controls/core/canvas/line.py b/sdk/python/packages/flet/src/flet/controls/core/canvas/line.py new file mode 100644 index 000000000..28c9a4ce6 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/canvas/line.py @@ -0,0 +1,41 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.core.canvas.shape import Shape +from flet.controls.painting import Paint +from flet.controls.types import OptionalNumber + + +@control("Line") +class Line(Shape): + """ + Draws a line between the given points using the given paint. The line is stroked, + the value of the `Paint.style` is ignored. + """ + + x1: OptionalNumber = None + """ + The x-axis coordinate of the line's starting point. + """ + + y1: OptionalNumber = None + """ + The y-axis coordinate of the line's starting point. + """ + + x2: OptionalNumber = None + """ + The x-axis coordinate of the line's end point. + """ + + y2: OptionalNumber = None + """ + The y-axis coordinate of the line's end point. + """ + + paint: Optional[Paint] = None + """ + A style to draw a line with. The value of this property is the instance of + [`Paint`](https://flet.dev/docs/reference/types/paint) class. + """ + diff --git a/sdk/python/packages/flet/src/flet/controls/core/canvas/oval.py b/sdk/python/packages/flet/src/flet/controls/core/canvas/oval.py new file mode 100644 index 000000000..a23e2a191 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/canvas/oval.py @@ -0,0 +1,42 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.core.canvas.shape import Shape +from flet.controls.painting import Paint +from flet.controls.types import OptionalNumber + + +@control("Oval") +class Oval(Shape): + """ + Draws an axis-aligned oval that fills the given axis-aligned rectangle with the + given `Paint`. Whether the oval is filled or stroked (or both) is controlled by + `Paint.style`. + """ + + x: OptionalNumber = None + """ + The x-axis coordinate of the oval's top left point. + """ + + y: OptionalNumber = None + """ + The y-axis coordinate of the oval's top left point. + """ + + width: OptionalNumber = None + """ + Width of the rectangle containing the oval. + """ + + height: OptionalNumber = None + """ + Height of the rectangle containing the oval. + """ + + paint: Optional[Paint] = None + """ + A style to draw an oval with. The value of this property is the instance of + [`Paint`](https://flet.dev/docs/reference/types/paint) class. + """ + diff --git a/sdk/python/packages/flet/src/flet/controls/core/canvas/path.py b/sdk/python/packages/flet/src/flet/controls/core/canvas/path.py new file mode 100644 index 000000000..31c64fc15 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/canvas/path.py @@ -0,0 +1,262 @@ +from dataclasses import dataclass, field +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.border_radius import OptionalBorderRadiusValue +from flet.controls.core.canvas.shape import Shape +from flet.controls.painting import Paint + + +@control("Path") +class Path(Shape): + """ + Draws the a path with given `elements` with the given `Paint`. + + Whether this shape is filled or stroked (or both) is controlled by `Paint.style`. + If the path is filled, then sub-paths within it are implicitly closed + (see `Path.Close`). + """ + + @dataclass(kw_only=True) + class PathElement: + _type: Optional[str] = field( + init=False, repr=False, compare=False, default=None + ) + + @dataclass + class MoveTo(PathElement): + """ + Starts a new sub-path at the given point (`x`,`y`). + """ + + x: float + y: float + + def __post_init__(self): + self._type = "moveto" + + @dataclass + class LineTo(PathElement): + """ + Adds a straight line segment from the current point to the given point + (`x`,`y`). + """ + + x: float + y: float + + def __post_init__(self): + self._type = "lineto" + + @dataclass + class QuadraticTo(PathElement): + """ + Adds a bezier segment that curves from the current point to the given point + (`x`,`y`), using the control points (`cp1x`,`cp1y`) and the weight `w`. + """ + + cp1x: float + cp1y: float + x: float + y: float + w: float = 1 + """ + If the weight is greater than 1, then the curve is a hyperbola; + if the weight equals 1, it's a parabola; + and if it is less than 1, it is an ellipse. + """ + + def __post_init__(self): + self._type = "conicto" + + @dataclass + class CubicTo(PathElement): + """ + Adds a cubic bezier segment that curves from the current point to the given + point (`x`,`y`), using the control points (`cp1x`,`cp1y`) and (`cp2x`,`cp2y`). + """ + + cp1x: float + cp1y: float + cp2x: float + cp2y: float + x: float + y: float + + def __post_init__(self): + self._type = "cubicto" + + @dataclass + class SubPath(PathElement): + """ + Adds the sub-path described by `elements` to the given point (`x`,`y`). + """ + + elements: list["Path.PathElement"] + x: float + y: float + + def __post_init__(self): + self._type = "subpath" + + @dataclass + class Arc(PathElement): + """ + Adds a new sub-path with one arc segment that consists of the arc that follows + the edge of the oval bounded by the given rectangle with top left corner at `x` + and `y` and dimensions `width` and `height`, from `start_angle` radians around + the oval up to `start_angle` + `sweep_angle` radians around the oval, with zero + radians being the point on the right hand side of the oval that crosses the + horizontal line that intersects the center of the rectangle and with positive + angles going clockwise around the oval. + """ + + x: float + """ + Top-left corner `x` of the rectangle bounding the arc. + """ + y: float + """ + Top-left corner `y` of the rectangle bounding the arc. + """ + width: float + """ + Width of the rectangle bounding the arc. + """ + height: float + """ + Height of the rectangle bounding the arc. + """ + start_angle: float + """ + Starting angle in radians of the arc. + """ + sweep_angle: float + """ + Sweep angle in radians from `start_angle`. + """ + + def __post_init__(self): + self._type = "arc" + + @dataclass + class ArcTo(PathElement): + """ + Appends up to four conic curves weighted to describe an oval of `radius` and + rotated by `rotation` (measured in degrees and clockwise). + + The first curve begins from the last point in the path and the last ends at `x` + and `y`. The curves follow a path in a direction determined by `clockwise` + (bool) and `large_arc` (bool) in such a way that the sweep angle is always less + than 360 degrees. + + A simple line is appended if either either radii are zero or the last point in + the path (`x`,`y`). The radii are scaled to fit the last path point if both are + greater than zero but too small to describe an arc. + """ + + x: float + """ + Destination `x` coordinate of arc endpoint. + """ + y: float + """ + Destination `y` coordinate of arc endpoint. + """ + radius: float = 0 + """ + Radius of the arc. + """ + rotation: float = 0 + """ + Rotation of the arc in degrees. + """ + large_arc: bool = False + """ + Whether to use the large arc sweep. + """ + clockwise: bool = True + """ + Whether the arc should be drawn clockwise. + """ + + def __post_init__(self): + self._type = "arcto" + + @dataclass + class Oval(PathElement): + """ + Adds a new sub-path that consists of a curve that forms the ellipse that fills + the given rectangle. + """ + + x: float + """ + The x-axis coordinate of the top-left of the bounding rectangle. + """ + y: float + """ + The y-axis coordinate of the top-left of the bounding rectangle. + """ + width: float + """ + Width of the bounding rectangle. + """ + height: float + """ + Height of the bounding rectangle. + """ + + def __post_init__(self): + self._type = "oval" + + @dataclass + class Rect(PathElement): + """ + Adds a rectangle as a new sub-path. + """ + + x: float + """ + The x-axis coordinate of the top-left of the rectangle. + """ + y: float + """ + The y-axis coordinate of the top-left of the rectangle. + """ + width: float + """ + Width of the rectangle. + """ + height: float + """ + Height of the rectangle. + """ + border_radius: OptionalBorderRadiusValue = None + """ + Optional border radius to round rectangle corners. + """ + + def __post_init__(self): + self._type = "rect" + + @dataclass + class Close(PathElement): + """ + Closes the last sub-path, as if a straight line had been drawn from the + current point to the first point of the sub-path. + """ + + def __post_init__(self): + self._type = "close" + + elements: list[PathElement] = field(default_factory=list) + """ + The list of path elements. + """ + + paint: Optional[Paint] = None + """ + A style to draw a path with. The value of this property is the instance of + [`Paint`](https://flet.dev/docs/reference/types/paint) class. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/core/canvas/points.py b/sdk/python/packages/flet/src/flet/controls/core/canvas/points.py new file mode 100644 index 000000000..1e388de08 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/canvas/points.py @@ -0,0 +1,61 @@ +from enum import Enum +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.core.canvas.shape import Shape +from flet.controls.painting import Paint +from flet.controls.transform import OffsetValue + + +class PointMode(Enum): + """ + Defines how a list of points is interpreted when drawing a set of points. + """ + + POINTS = "points" + """ + Draw each point separately. If the `Paint.stroke_cap` is `StrokeCap.ROUND`, + then each point is drawn as a circle with the diameter of the `Paint.stroke_width`, + filled as described by the `Paint` (ignoring `Paint.style`). Otherwise, each point + is drawn as an axis-aligned square with sides of length `Paint.stroke_width`, filled + as described by the `Paint` (ignoring `Paint.style`). + """ + + LINES = "lines" + """ + Draw each sequence of two points as a line segment. If the number of points is odd, + then the last point is ignored. The lines are stroked as described by the `Paint` + (ignoring `Paint.style`). + """ + + POLYGON = "polygon" + """ + Draw the entire sequence of point as one line. The lines are stroked as described + by the `Paint` (ignoring `Paint.style`). + """ + + +@control("Points") +class Points(Shape): + """ + Draws a sequence of points according to the given `point_mode`. + """ + + points: Optional[list[OffsetValue]] = None + """ + The list of `ft.Offset` describing points. + """ + + point_mode: Optional[PointMode] = None + """ + Defines how a list of points is interpreted when drawing a set of points. + + Value is of type [`PointMode`](https://flet.dev/docs/reference/types/pointmode). + """ + + paint: Optional[Paint] = None + """ + A style to draw points with. The value of this property is the instance of + [`Paint`](https://flet.dev/docs/reference/types/paint) class. + """ + diff --git a/sdk/python/packages/flet/src/flet/controls/core/canvas/rect.py b/sdk/python/packages/flet/src/flet/controls/core/canvas/rect.py new file mode 100644 index 000000000..a0f04e886 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/canvas/rect.py @@ -0,0 +1,48 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.border_radius import OptionalBorderRadiusValue +from flet.controls.core.canvas.shape import Shape +from flet.controls.painting import Paint +from flet.controls.types import OptionalNumber + + +@control("Rect") +class Rect(Shape): + """ + Draws a rectangle. + """ + + x: OptionalNumber = None + """ + The x-axis coordinate of the rectangle's top left point. + """ + + y: OptionalNumber = None + """ + The y-axis coordinate of the rectangle's top left point. + """ + + width: OptionalNumber = None + """ + Width of the rectangle. + """ + + height: OptionalNumber = None + """ + Height of the rectangle. + """ + + border_radius: OptionalBorderRadiusValue = None + """ + Border radius of the rectangle. + + Value is of type [`BorderRadius`](https://flet.dev/docs/reference/types/borderradius). + """ + + paint: Optional[Paint] = None + """ + A style to draw a rectangle with. The value of this property is the instance of + [`Paint`](https://flet.dev/docs/reference/types/paint) class. + """ + diff --git a/sdk/python/packages/flet/src/flet/controls/core/canvas/shadow.py b/sdk/python/packages/flet/src/flet/controls/core/canvas/shadow.py new file mode 100644 index 000000000..8676db38e --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/canvas/shadow.py @@ -0,0 +1,38 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.core.canvas.path import Path +from flet.controls.core.canvas.shape import Shape +from flet.controls.types import OptionalColorValue, OptionalNumber + + +@control("Shadow") +class Shadow(Shape): + """ + Draws a shadow for a `path` representing the given material `elevation`. + + The `transparent_occluder` argument should be `True` if the occluding object + is not opaque. + """ + + path: Optional[list[Path.PathElement]] = None + """ + The list of `Path.PathElement` objects describing the path. + """ + + color: OptionalColorValue = None + """ + Shadow [color](https://flet.dev/docs/reference/colors). + """ + + elevation: OptionalNumber = None + """ + Shadow elevation. + """ + + transparent_occluder: bool = False + """ + `True` if the occluding object is not opaque. + + Defaults to `False`. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/core/canvas/shape.py b/sdk/python/packages/flet/src/flet/controls/core/canvas/shape.py new file mode 100644 index 000000000..20640d7c7 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/canvas/shape.py @@ -0,0 +1,5 @@ +from flet.controls.control import Control + + +class Shape(Control): + pass diff --git a/sdk/python/packages/flet/src/flet/controls/core/canvas/text.py b/sdk/python/packages/flet/src/flet/controls/core/canvas/text.py new file mode 100644 index 000000000..0a35f9553 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/canvas/text.py @@ -0,0 +1,88 @@ +from typing import Optional + +from flet.controls.alignment import Alignment +from flet.controls.base_control import control +from flet.controls.core.canvas.shape import Shape +from flet.controls.core.text_span import TextSpan +from flet.controls.text_style import TextStyle +from flet.controls.types import OptionalNumber, TextAlign + +__all__ = ["Text"] + + +@control("Text") +class Text(Shape): + """ + Draws `text` with `style` in the given point (`x`, `y`). + """ + + x: OptionalNumber = None + """ + The x-axis coordinate of the text's `alignment` point. + """ + + y: OptionalNumber = None + """ + The y-axis coordinate of the text's `alignment` point. + """ + + text: Optional[str] = None + """ + The text to draw. + """ + + style: Optional[TextStyle] = None + """ + A text style to draw `text` and `spans` with. The value is the instance of + [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) class. + """ + + spans: Optional[list[TextSpan]] = None + """ + The list of [`TextSpan`](https://flet.dev/docs/reference/types/textspan) + objects to build a rich text paragraph. + """ + + alignment: Optional[Alignment] = None + """ + A point within a text rectangle to determine its position and rotation center. + + Value is of type [`Alignment`](https://flet.dev/docs/reference/types/alignment) + and defaults to `alignment.top_left`. + """ + + text_align: Optional[TextAlign] = None + """ + Text horizontal align. + + Value is of type [`TextAlign`](https://flet.dev/docs/reference/types/textalign) + and defaults to `TextAlign.LEFT`. + """ + + max_lines: Optional[int] = None + """ + The maximum number of lines painted. Lines beyond this number are silently + dropped. For example, if `max_lines = 1`, then only one line is rendered. + If `max_lines = None`, but `ellipsis != None`, then lines after the first one + that overflows the width constraints are dropped. + """ + + max_width: OptionalNumber = None + """ + The maximum width of the painted text. + + Defaults to `None` - infinity. + """ + + ellipsis: Optional[str] = None + """ + String used to ellipsize overflowing text. + """ + + rotate: OptionalNumber = None + """ + Text rotation in radians. Text is rotated around the point determined by + `alignment`. See code examples above. + ``` + """ + diff --git a/sdk/python/packages/flet/src/flet/controls/core/column.py b/sdk/python/packages/flet/src/flet/controls/core/column.py new file mode 100644 index 000000000..d2bbb637e --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/column.py @@ -0,0 +1,79 @@ +from dataclasses import field + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.scrollable_control import ScrollableControl +from flet.controls.types import CrossAxisAlignment, MainAxisAlignment, Number + +__all__ = ["Column"] + + +@control("Column") +class Column(ConstrainedControl, ScrollableControl, AdaptiveControl): + """ + Container allows to decorate a control with background color and border and + position it with padding, margin and alignment. + + Online docs: https://flet.dev/docs/controls/column + """ + + controls: list[Control] = field(default_factory=list) + """ + A list of Controls to display inside the Column. + """ + + alignment: MainAxisAlignment = MainAxisAlignment.START + """ + How the child Controls should be placed vertically. + + Value is of type + [`MainAxisAlignment`](https://flet.dev/docs/reference/types/mainaxisalignment). + """ + + horizontal_alignment: CrossAxisAlignment = CrossAxisAlignment.START + """ + How the child Controls should be placed horizontally. + + Value is of type + [`CrossAxisAlignment`](https://flet.dev/docs/reference/types/crossaxisalignment) + and defaults to `CrossAxisAlignment.START`. + """ + + spacing: Number = 10 + """ + Spacing between the `controls`. + + It is applied only when `alignment` is set to `MainAxisAlignment.START`, + `MainAxisAlignment.END` or `MainAxisAlignment.CENTER`. + + Default value is `10` virtual pixels. + """ + + tight: bool = False + """ + Specifies how much space should be occupied vertically. + + Defaults to `False` - allocate all space to children. + """ + + wrap: bool = False + """ + When set to `True` the Column will put child controls into additional columns + (runs) if they don't fit a single column. + """ + + run_spacing: Number = 10 + """ + Spacing between runs when `wrap=True`. Default value is 10. + """ + + run_alignment: MainAxisAlignment = MainAxisAlignment.START + """ + How the runs should be placed in the cross-axis when `wrap=True`. + + Value is of type + [`MainAxisAlignment`](https://flet.dev/docs/reference/types/mainaxisalignment) + and defaults to `MainAxisAlignment.START`. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/core/dismissible.py b/sdk/python/packages/flet/src/flet/controls/core/dismissible.py new file mode 100644 index 000000000..32f8c11bc --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/dismissible.py @@ -0,0 +1,166 @@ +import asyncio +from dataclasses import dataclass, field + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.colors import Colors +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import ( + Event, + OptionalControlEventHandler, + OptionalEventHandler, +) +from flet.controls.duration import Duration, DurationValue +from flet.controls.material.container import Container +from flet.controls.material.snack_bar import DismissDirection +from flet.controls.types import ( + Number, + OptionalNumber, +) + +__all__ = ["Dismissible", "DismissibleDismissEvent", "DismissibleUpdateEvent"] + + +@dataclass +class DismissibleDismissEvent(Event["Dismissible"]): + direction: DismissDirection + + +@dataclass +class DismissibleUpdateEvent(Event["Dismissible"]): + direction: DismissDirection + progress: float + reached: bool + previous_reached: bool + + +@control("Dismissible") +class Dismissible(ConstrainedControl, AdaptiveControl): + """ + A control that can be dismissed by dragging in the indicated `dismiss_direction`. + When dragged or flung in the specified `dismiss_direction`, its content smoothly + slides out of view. + + After completing the sliding animation, if a `resize_duration` is provided, this + control further animates its height (or width, depending on what is perpendicular + to the `dismiss_direction`), gradually reducing it to zero over the specified + `resize_duration`. + + Online Docs: https://flet.dev/docs/controls/dismissible + """ + + content: Control + """ + A child Control contained by the Dismissible. + """ + + background: Control = Container(bgcolor=Colors.TRANSPARENT) + """ + A Control that is stacked behind the `content`. + + If `secondary_background` is also specified, then this control only appears when + the content has been dragged down or to the right. + """ + + secondary_background: Control = Container(bgcolor=Colors.TRANSPARENT) + """ + A control that is stacked behind the `content` and is exposed when the `content` + has been dragged up or to the left. + + Has no effect if `background` is not specified. + """ + + dismiss_direction: DismissDirection = DismissDirection.HORIZONTAL + """ + The direction in which the control can be dismissed. + + Value is of type + [`DismissDirection`](https://flet.dev/docs/reference/types/dismissdirection). + """ + + dismiss_thresholds: dict[DismissDirection, OptionalNumber] = field( + default_factory=dict + ) + + """ + The offset threshold the item has to be dragged in order to be considered dismissed. + + Ex: a threshold of `0.4` (the default) means that the item has to be dragged + _at least_ 40% in order for it to be dismissed. + + It is specified as a dictionary where the key is of type + [`DismissDirection`](https://flet.dev/docs/reference/types/dismissdirection) and + the value is the threshold (fractional/decimal value between `0.0` and `1.0`): + + ```python + ft.Dismissible( + # ... + dismiss_thresholds={ + ft.DismissDirection.VERTICAL: 0.1, + ft.DismissDirection.START_TO_END: 0.7 + } + ) + ``` + """ + + movement_duration: DurationValue = field( + default_factory=lambda: Duration(milliseconds=200) + ) + """ + The duration for card to dismiss or to come back to original position if not + dismissed. + """ + + resize_duration: DurationValue = field( + default_factory=lambda: Duration(milliseconds=300) + ) + """ + The amount of time the control will spend contracting before `on_dismiss` is called. + """ + + cross_axis_end_offset: Number = 0.0 + """ + Specifies the end offset along the main axis once the card has been dismissed. + + If non-zero value is given then widget moves in cross direction depending on whether + it is positive or negative. + """ + + on_update: OptionalEventHandler[DismissibleUpdateEvent] = None + """ + Fires when this control has been dragged. + """ + + on_dismiss: OptionalEventHandler[DismissibleDismissEvent] = None + """ + Fires when this control has been dismissed, after finishing resizing. + """ + + on_confirm_dismiss: OptionalEventHandler[DismissibleDismissEvent] = None + """ + Gives the app an opportunity to confirm or veto a pending dismissal. The widget + cannot be dragged again until the returned this pending dismissal is resolved. + + To resolve the pending dismissal, call the `confirm_dismiss(dismiss)` method + passing it a boolean representing the decision. If `True`, then the control will be + dismissed, otherwise it will be moved back to its original location. + + See the example at the top of this page for a possible implementation. + """ + + on_resize: OptionalControlEventHandler["Dismissible"] = None + """ + Fires when this control changes size, for example, when contracting before + being dismissed. + """ + + def before_update(self): + super().before_update() + assert self.content.visible, "content must be visible" + + async def confirm_dismiss_async(self, dismiss: bool): + await self._invoke_method_async("confirm_dismiss", {"dismiss": dismiss}) + + def confirm_dismiss(self, dismiss: bool): + asyncio.create_task(self.confirm_dismiss_async(dismiss)) diff --git a/sdk/python/packages/flet/src/flet/controls/core/drag_target.py b/sdk/python/packages/flet/src/flet/controls/core/drag_target.py new file mode 100644 index 000000000..7d38ee91a --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/drag_target.py @@ -0,0 +1,100 @@ +from dataclasses import dataclass +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.control_event import Event, OptionalEventHandler +from flet.controls.transform import Offset + +__all__ = [ + "DragTarget", + "DragTargetEvent", + "DragWillAcceptEvent", + "DragTargetLeaveEvent", +] + + +@dataclass +class DragWillAcceptEvent(Event["DragTarget"]): + accept: bool + src_id: int + + +@dataclass +class DragTargetEvent(Event["DragTarget"]): + src_id: int + x: float + y: float + + @property + def offset(self) -> Offset: + return Offset(self.x, self.y) + + +@dataclass +class DragTargetLeaveEvent(Event["DragTarget"]): + src_id: Optional[int] + + +@control("DragTarget") +class DragTarget(Control): + """ + A control that completes drag operation when a `Draggable` widget is dropped. + + When a draggable is dragged on top of a drag target, the drag target is asked + whether it will accept the data the draggable is carrying. The drag target will + accept incoming drag if it belongs to the same group as draggable. If the user + does drop the draggable on top of the drag target (and the drag target has + indicated that it will accept the draggable's data), then the drag target is + asked to accept the draggable's data. + + Online docs: https://flet.dev/docs/controls/dragtarget + """ + + content: Control + """ + The `Control` that is a visual representation of the drag target. + """ + + group: str = "default" + """ + The group this target belongs to. Note that for this target to accept an + incoming drop from a [`Draggable`](https://flet.dev/docs/controls/draggable), they + must both be in the same group. + """ + + on_will_accept: OptionalEventHandler[DragWillAcceptEvent] = None + """ + Fires when a draggable is dragged on this target. + + `data` field of event details contains `true` (String) if both the draggable + and this target are in the same `group`; otherwise `false` (String). + """ + + on_accept: OptionalEventHandler[DragTargetEvent] = None + """ + Fires when the user does drop an acceptable (same `group`) draggable on + this target. + + Event handler argument is an instance of + [`DragTargetEvent`](https://flet.dev/docs/reference/types/dragtargetevent). + + Use `page.get_control(e.src_id)` to retrieve Control reference by its ID. + """ + + on_leave: OptionalEventHandler[DragTargetLeaveEvent] = None + """ + Fires when a draggable leaves this target. + """ + + on_move: OptionalEventHandler[DragTargetEvent] = None + """ + Fires when a draggable moves within this target. + + Event handler argument is of type + [`DragTargetEvent`](https://flet.dev/docs/reference/types/dragtargetevent). + """ + + def before_update(self): + super().before_update() + assert self.content.visible, "content must be visible" diff --git a/sdk/python/packages/flet/src/flet/controls/core/draggable.py b/sdk/python/packages/flet/src/flet/controls/core/draggable.py new file mode 100644 index 000000000..1049e2cec --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/draggable.py @@ -0,0 +1,106 @@ +from typing import Optional + +from flet.controls.alignment import Axis +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler + +__all__ = ["Draggable"] + + +@control("Draggable") +class Draggable(Control): + """ + A control that can be dragged from to a `DragTarget`. + + When a draggable control recognizes the start of a drag gesture, it displays a + `content_feedback` control that tracks the user's finger across the screen. If the + user lifts their finger while on top of a `DragTarget`, that target is given the + opportunity to complete drag-and-drop flow. + + Online docs: https://flet.dev/docs/controls/draggable + """ + + content: Control + """ + `Draggable` control displays [`content`](#content) when zero drags are under way. + + If [`content_when_dragging`](#content_when_dragging) is not `None`, this control + instead displays `content_when_dragging` when one or more drags are underway. + Otherwise, this control always displays `content`. + """ + + group: str = "default" + """ + A group this draggable belongs to. + + For [`DragTarget`](https://flet.dev/docs/controls/dragtarget) to accept incoming + drag both `Draggable` and `DragTarget` must be in the same `group`. + """ + + content_when_dragging: Optional[Control] = None + """ + The `Control` to display instead of `content` when one or more drags are under way. + + If this is `None`, then this widget will always display `content` (and so the drag + source representation will not change while a drag is under way). + """ + + content_feedback: Optional[Control] = None + """ + The `Control` to show under the pointer when a drag is under way. + """ + + axis: Optional[Axis] = None + """ + The axis to restrict this draggable's movement. + + When axis is set to `Axis.HORIZONTAL`, this control can only be dragged + horizontally. When axis is set to `Axis.VERTICAL`, this control can only be dragged + vertically. + + Value is of type [`Axis`](https://flet.dev/docs/reference/types/axis) and defaults + to `None` - no restriction. + """ + + affinity: Optional[Axis] = None + """ + Controls how this control competes with other gestures to initiate a drag. + + If set to `None`, this widget initiates a drag as soon as it recognizes a tap down + gesture, regardless of any directionality. + + If set to `Axis.HORIZONTAL` or `Axis.VERTICAL`, then this control will compete with + other horizontal (or vertical, respectively) gestures. + + Value is of type [`Axis`](https://flet.dev/docs/reference/types/axis). + """ + + max_simultaneous_drags: Optional[int] = None + """ + The number of simultaneous drags to support. + + - Set this to `0` if you want to prevent the draggable from actually being dragged. + - Set this to `1` if you want to only allow the drag source to have one item dragged + at a time. In this case, consider supplying an "empty" widget for + `content_when_dragging` to create the illusion of actually moving `content`. + + Defaults to `None` - no limit. + """ + + on_drag_start: OptionalControlEventHandler["Draggable"] = None + """ + Fires when this draggable starts being dragged. + """ + + on_drag_complete: OptionalControlEventHandler["Draggable"] = None + """ + Fires when this draggable is dropped and accepted by a DragTarget. + """ + + def before_update(self): + super().before_update() + assert self.content.visible, "content must be visible" + assert self.max_simultaneous_drags is None or ( + self.max_simultaneous_drags >= 0 + ), "max_simultaneous_drags must be greater than or equal to 0" diff --git a/sdk/python/packages/flet/src/flet/controls/core/flet_app.py b/sdk/python/packages/flet/src/flet/controls/core/flet_app.py new file mode 100644 index 000000000..28b994507 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/flet_app.py @@ -0,0 +1,54 @@ +from typing import Any, Optional + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler + +__all__ = ["FletApp"] + + +@control("FletApp") +class FletApp(ConstrainedControl): + """ + Renders another Flet app in the current app, similar to HTML IFrame, but for Flet. + """ + + url: Optional[str] = None + """ + Flet app URL, e.g. `http://localhost:8550` or `flet.sock`. + """ + + args: Optional[dict[str, Any]] = None + """ + TBD + """ + + force_pyodide: bool = False + """ + TBD + """ + + reconnect_interval_ms: Optional[int] = None + """ + Delay, in milliseconds, between reconnection attempts. + """ + + reconnect_timeout_ms: Optional[int] = None + """ + Total time to try reconnecting. + """ + + show_app_startup_screen: bool = False + """ + TBD + """ + + app_startup_screen_message: Optional[str] = None + """ + TBD + """ + + on_error: OptionalControlEventHandler["FletApp"] = None + """ + Fires when a connection or any unhandled error occurs. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/core/gesture_detector.py b/sdk/python/packages/flet/src/flet/controls/core/gesture_detector.py new file mode 100644 index 000000000..fc22e624b --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/gesture_detector.py @@ -0,0 +1,346 @@ +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalEventHandler +from flet.controls.events import ( + DragEndEvent, + DragStartEvent, + DragUpdateEvent, + HoverEvent, + LongPressEndEvent, + LongPressStartEvent, + ScaleEndEvent, + ScaleStartEvent, + ScaleUpdateEvent, + ScrollEvent, + TapEvent, +) +from flet.controls.types import MouseCursor, PointerDeviceType + +__all__ = ["GestureDetector"] + + +@control("GestureDetector") +class GestureDetector(ConstrainedControl, AdaptiveControl): + """ + A control that detects gestures. + + Attempts to recognize gestures that correspond to its non-null callbacks. + + If this control has a content, it defers to that child control for its sizing + behavior. If it does not have a content, it grows to fit the parent instead. + + Online docs: https://flet.dev/docs/controls/gesturedetector + """ + + content: Optional[Control] = None + """ + A child Control contained by the gesture detector. + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + The mouse cursor for mouse pointers that are hovering over the control. + + Value is of type + [`MouseCursor`](https://flet.dev/docs/reference/types/mousecursor). + """ + + drag_interval: int = 0 + """ + Throttling in milliseconds for horizontal drag, vertical drag and pan update events. + + When a user moves a pointer a lot of events are being generated to do precise + tracking. `drag_interval` allows sending drag update events to a Flet program every + X milliseconds, thus preserving the bandwidth (web and mobile apps). + + Default is `0` - no throttling, all events are sent to a Flet program, very smooth + tracking. + """ + + hover_interval: int = 0 + """ + Throttling in milliseconds for `on_hover` event. + """ + + multi_tap_touches: int = 0 + """ + The minimum number of pointers to trigger `on_multi_tap` event. + """ + + exclude_from_semantics: bool = False + """ + TBD + """ + + trackpad_scroll_causes_scale: bool = False + """ + TBD + """ + + allowed_devices: Optional[list[PointerDeviceType]] = None + """ + TBD + """ + + on_tap: OptionalEventHandler[TapEvent["GestureDetector"]] = None + """ + A tap with a primary button has occurred. + """ + + on_tap_down: OptionalEventHandler[TapEvent["GestureDetector"]] = None + """ + A pointer that might cause a tap with a primary button has contacted the screen + at a particular location. + + Event handler argument is of type + [`TapEvent`](https://flet.dev/docs/reference/types/tapevent). + """ + + on_tap_up: OptionalEventHandler[TapEvent["GestureDetector"]] = None + """ + A pointer that will trigger a tap with a primary button has stopped contacting the + screen at a particular location. + + Event handler argument is of type + [`TapEvent`](https://flet.dev/docs/reference/types/tapevent). + """ + + on_multi_tap: OptionalEventHandler[TapEvent["GestureDetector"]] = None + """ + Triggered when multiple pointers contacted the screen. + + Event handler argument is of type + [`MultiTapEvent`](https://flet.dev/docs/reference/types/multitapevent). + """ + + on_multi_long_press: OptionalEventHandler[LongPressEndEvent["GestureDetector"]] = ( + None + ) + """ + Called when a long press gesture with multiple pointers has been recognized. + """ + + on_secondary_tap: OptionalEventHandler[TapEvent["GestureDetector"]] = None + """ + A tap with a secondary button has occurred. + """ + + on_secondary_tap_down: OptionalEventHandler[TapEvent["GestureDetector"]] = None + """ + A pointer that might cause a tap with a secondary button has contacted the screen + at a particular location. + + Event handler argument is of type + [`TapEvent`](https://flet.dev/docs/reference/types/tapevent). + """ + + on_secondary_tap_up: OptionalEventHandler[TapEvent["GestureDetector"]] = None + """ + A pointer that will trigger a tap with a secondary button has stopped contacting the + screen at a particular location. + + Event handler argument is of type + [`TapEvent`](https://flet.dev/docs/reference/types/tapevent). + """ + + on_long_press_start: OptionalEventHandler[ + LongPressStartEvent["GestureDetector"] + ] = None + """ + Called when a long press gesture with a primary button has been recognized. + + Triggered when a pointer has remained in contact with the screen at the same + location for a long period of time. + + Event handler argument is of type + [`LongPressStartEvent`](https://flet.dev/docs/reference/types/longpressstartevent). + """ + + on_long_press_end: OptionalEventHandler[LongPressEndEvent["GestureDetector"]] = None + """ + A pointer that has triggered a long-press with a primary button has stopped + contacting the screen. + + Event handler argument is of type + [`LongPressEndEvent`](https://flet.dev/docs/reference/types/longpressendevent). + """ + + on_secondary_long_press_start: OptionalEventHandler[ + LongPressStartEvent["GestureDetector"] + ] = None + """ + Called when a long press gesture with a secondary button has been recognized. + + Triggered when a pointer has remained in contact with the screen at the same + location for a long period of time. + + Event handler argument is of type + [`LongPressStartEvent`](https://flet.dev/docs/reference/types/longpressstartevent). + """ + + on_secondary_long_press_end: OptionalEventHandler[ + LongPressEndEvent["GestureDetector"] + ] = None + """ + A pointer that has triggered a long-press with a secondary button has stopped + contacting the screen. + + Event handler argument is of type + [`LongPressEndEvent`](https://flet.dev/docs/reference/types/longpressendevent). + """ + + on_double_tap: OptionalEventHandler[TapEvent["GestureDetector"]] = None + """ + The user has tapped the screen with a primary button at the same location twice + in quick succession. + """ + + on_double_tap_down: OptionalEventHandler[TapEvent["GestureDetector"]] = None + """ + A pointer that might cause a double tap has contacted the screen at a particular + location. + + Triggered immediately after the down event of the second tap. + + Event handler argument is of type + [`TapEvent`](https://flet.dev/docs/reference/types/tapevent). + """ + + on_horizontal_drag_start: OptionalEventHandler[ + DragStartEvent["GestureDetector"] + ] = None + """ + A pointer has contacted the screen with a primary button and has begun to move + horizontally. + + Event handler argument is of type + [`DragStartEvent`](https://flet.dev/docs/reference/types/dragstartevent). + """ + + on_horizontal_drag_update: OptionalEventHandler[ + DragUpdateEvent["GestureDetector"] + ] = None + """ + A pointer that is in contact with the screen and moving horizontally has moved in + the horizontal direction. + + Event handler argument is of type + [`DragUpdateEvent`](https://flet.dev/docs/reference/types/dragupdateevent). + """ + + on_horizontal_drag_end: OptionalEventHandler[DragEndEvent["GestureDetector"]] = None + """ + A pointer moving horizontally is no longer in contact and was moving at a specific + velocity. + + Event handler argument is of type + [`DragEndEvent`](https://flet.dev/docs/reference/types/dragendevent). + """ + + on_vertical_drag_start: OptionalEventHandler[DragStartEvent["GestureDetector"]] = ( + None + ) + """ + A pointer has contacted the screen and has begun to move vertically. + + Event handler argument is of type + [`DragStartEvent`](https://flet.dev/docs/reference/types/dragstartevent). + """ + + on_vertical_drag_update: OptionalEventHandler[ + DragUpdateEvent["GestureDetector"] + ] = None + """ + A pointer moving vertically has moved in the vertical direction. + + Event handler argument is of type + [`DragUpdateEvent`](https://flet.dev/docs/reference/types/dragupdateevent). + """ + + on_vertical_drag_end: OptionalEventHandler[DragEndEvent["GestureDetector"]] = None + """ + A pointer moving vertically is no longer in contact and was moving at a specific + velocity. + + Event handler argument is of type + [`DragEndEvent`](https://flet.dev/docs/reference/types/dragendevent). + """ + + on_pan_start: OptionalEventHandler[DragStartEvent["GestureDetector"]] = None + """ + A pointer has contacted the screen and has begun to move. + + Event handler argument is of type + [`DragStartEvent`](https://flet.dev/docs/reference/types/dragstartevent). + """ + + on_pan_update: OptionalEventHandler[DragUpdateEvent["GestureDetector"]] = None + """ + A pointer that is in contact with the screen and moving has moved again. + + Event handler argument is of type + [`DragUpdateEvent`](https://flet.dev/docs/reference/types/dragupdateevent). + """ + + on_pan_end: OptionalEventHandler[DragEndEvent["GestureDetector"]] = None + """ + A pointer is no longer in contact and was moving at a specific velocity. + + Event handler argument is of type + [`DragEndEvent`](https://flet.dev/docs/reference/types/dragendevent). + """ + + on_scale_start: OptionalEventHandler[ScaleStartEvent["GestureDetector"]] = None + """ + The pointers in contact with the screen have established a focal point and initial + scale of `1.0`. + + Event handler argument is of type + [`ScaleStartEvent`](https://flet.dev/docs/reference/types/scalestartevent). + """ + + on_scale_update: OptionalEventHandler[ScaleUpdateEvent["GestureDetector"]] = None + """ + Event handler argument is of type + [`ScaleUpdateEvent`](https://flet.dev/docs/reference/types/scaleupdateevent). + """ + + on_scale_end: OptionalEventHandler[ScaleEndEvent["GestureDetector"]] = None + """ + Event handler argument is of type + [`ScaleEndEvent`](https://flet.dev/docs/reference/types/scaleendevent). + """ + + on_hover: OptionalEventHandler[HoverEvent["GestureDetector"]] = None + """ + Triggered when a mouse pointer has entered this control. + + Event handler argument is of type + [`HoverEvent`](https://flet.dev/docs/reference/types/hoverevent). + """ + + on_enter: OptionalEventHandler[HoverEvent["GestureDetector"]] = None + """ + Triggered when a mouse pointer has entered this control. + + Event handler argument is of type + [`HoverEvent`](https://flet.dev/docs/reference/types/hoverevent). + """ + + on_exit: OptionalEventHandler[HoverEvent["GestureDetector"]] = None + """ + Triggered when a mouse pointer has exited this control. + + Event handler argument is of type + [`HoverEvent`](https://flet.dev/docs/reference/types/hoverevent). + """ + + on_scroll: OptionalEventHandler[ScrollEvent["GestureDetector"]] = None + """ + Event handler argument is of type + [`ScrollEvent`](https://flet.dev/docs/reference/types/scrollevent). + """ diff --git a/sdk/python/packages/flet/src/flet/controls/core/grid_view.py b/sdk/python/packages/flet/src/flet/controls/core/grid_view.py new file mode 100644 index 000000000..91dced23d --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/grid_view.py @@ -0,0 +1,117 @@ +from dataclasses import field +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.padding import OptionalPaddingValue +from flet.controls.scrollable_control import ScrollableControl +from flet.controls.types import ClipBehavior, Number, OptionalNumber + +__all__ = ["GridView"] + + +@control("GridView") +class GridView(ConstrainedControl, ScrollableControl, AdaptiveControl): + """ + A scrollable, 2D array of controls. + + GridView is very effective for large lists (thousands of items). Prefer it over + wrapping `Column` or `Row` for smooth scrolling. + + Online docs: https://flet.dev/docs/controls/gridview + """ + + controls: list[Control] = field(default_factory=list) + """ + A list of `Control`s to display inside GridView. + """ + + horizontal: bool = False + """ + `True` to layout GridView items horizontally. + """ + + reverse: bool = False + """ + Whether the scroll view scrolls in the reading direction. + + For example, if the reading direction is left-to-right and `horizontal` is `True`, + then the scroll view scrolls from left to right when `reverse` is `False` + and from right to left when `reverse` is `True`. + + Similarly, if `horizontal` is `False`, then the scroll view scrolls from top + to bottom when `reverse` is `False` and from bottom to top when `reverse` is `True`. + + Defaults to `False`. + """ + + runs_count: int = 1 + """ + The number of children in the cross axis. + """ + + max_extent: Optional[int] = None + """ + The maximum width or height of the grid item. + """ + + spacing: Number = 10 + """ + The number of logical pixels between each child along the main axis. + """ + + run_spacing: Number = 10 + """ + The number of logical pixels between each child along the cross axis. + """ + + child_aspect_ratio: Number = 1.0 + """ + The ratio of the cross-axis to the main-axis extent of each child. + """ + + padding: OptionalPaddingValue = None + """ + The amount of space by which to inset the children. + + Padding is an instance of + [`Padding`](https://flet.dev/docs/reference/types/padding). + """ + + clip_behavior: Optional[ClipBehavior] = None + """ + The content will be clipped (or not) according to this option. + + Value is of type + [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) and defaults + to `ClipBehavior.HARD_EDGE`. + """ + + semantic_child_count: Optional[int] = None + """ + The number of children that will contribute semantic information. + """ + + cache_extent: OptionalNumber = None + """ + Items that fall in the cache area (area before or after the visible area that are + about to become visible when the user scrolls) are laid out even though they are + not (yet) visible on screen. + + The cacheExtent describes how many pixels the cache area extends before the leading + edge and after the trailing edge of the viewport. + + The total extent, which the viewport will try to cover with `controls`, is + `cache_extent` before the leading edge + extent of the main axis + `cache_extent` + after the trailing edge. + """ + + build_controls_on_demand: bool = True + """ + TBD + """ + + def __contains__(self, item): + return item in self.controls diff --git a/sdk/python/packages/flet/src/flet/controls/core/icon.py b/sdk/python/packages/flet/src/flet/controls/core/icon.py new file mode 100644 index 000000000..3f47687c7 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/icon.py @@ -0,0 +1,95 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.box import ShadowValue +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.types import BlendMode, IconValue, OptionalColorValue, OptionalNumber + +__all__ = ["Icon"] + + +@control("Icon") +class Icon(ConstrainedControl): + """ + Displays a Material icon. + + Icon browser: https://flet-icons-browser.fly.dev/#/ + + Online docs: https://flet.dev/docs/controls/icon + """ + + name: IconValue + """ + The name of the icon. + + You can search through the list of all available icons using open-source + [Icons browser](https://gallery.flet.dev/icons-browser/) app + [written in Flet](https://github.com/flet-dev/examples/blob/main/python/apps/icons-browser/main.py). + """ + + color: OptionalColorValue = None + """ + Icon [color](https://flet.dev/docs/reference/colors). + """ + + size: OptionalNumber = None + """ + The icon's size. + + Defaults to `24`. + """ + + semantics_label: Optional[str] = None + """ + The semantics label for this icon. + + It is not shown to the in the UI, but is announced in accessibility modes + (e.g. TalkBack/VoiceOver). + """ + + shadows: Optional[ShadowValue] = None + """ + TBD + """ + + fill: OptionalNumber = None + """ + TBD + """ + + apply_text_scaling: Optional[bool] = None + """ + TBD + """ + + grade: OptionalNumber = None + """ + TBD + """ + + weight: OptionalNumber = None + """ + TBD + """ + + optical_size: OptionalNumber = None + """ + TBD + """ + + blend_mode: Optional[BlendMode] = None + """ + TBD + """ + + def before_update(self): + super().before_update() + assert self.fill is None or ( + 0.0 <= self.fill <= 1.0 + ), "fill must be between 0.0 and 1.0 inclusive" + assert self.weight is None or ( + self.weight > 0.0 + ), "weight must be strictly greater than 0.0" + assert self.optical_size is None or ( + self.optical_size > 0.0 + ), "optical_size must be strictly greater than 0.0" diff --git a/sdk/python/packages/flet/src/flet/controls/core/image.py b/sdk/python/packages/flet/src/flet/controls/core/image.py new file mode 100644 index 000000000..ccc07ebec --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/image.py @@ -0,0 +1,162 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.border_radius import OptionalBorderRadiusValue +from flet.controls.box import BoxFit, FilterQuality +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import OptionalControl +from flet.controls.types import ( + BlendMode, + ImageRepeat, + OptionalBool, + OptionalColorValue, + OptionalInt, + OptionalString, +) + +__all__ = ["Image"] + + +@control("Image") +class Image(ConstrainedControl): + """ + A control that displays an image. + + Online docs: https://flet.dev/docs/controls/image + """ + + src: OptionalString = None + """ + The image source. + + This could be an external URL or a local + [asset file](https://flet.dev/docs/cookbook/assets). + """ + + src_base64: OptionalString = None + """ + Displays an image from Base-64 encoded string, for example: + + https://github.com/flet-dev/examples/blob/main/python/controls/information-displays/image/image-base64.py + + Use `base64` command (Linux, macOS, WSL) to convert file to Base64 format: + + ``` + base64 -i -o + ``` + + On Windows you can use PowerShell to encode string into Base64 format: + + ```posh + [convert]::ToBase64String((Get-Content -path "your_file_path" -Encoding byte)) + ``` + """ + + src_bytes: Optional[bytes] = None + """ + TBD + """ + + error_content: OptionalControl = None + """ + Fallback `Control` to display if the image cannot be loaded from the source. + """ + + repeat: Optional[ImageRepeat] = None + """ + How to paint any portions of the layout bounds not covered by the image. + + Values is of type + [`ImageRepeat`](https://flet.dev/docs/reference/types/imagerepeat) + and defaults to `ImageRepeat.NO_REPEAT`. + """ + + fit: Optional[BoxFit] = None + """ + How to inscribe the image into the space allocated during layout. + + Value is of type + [`ImageFit`](https://flet.dev/docs/reference/types/imagefit) and defaults to + `ImageFit.NONE`. + """ + + border_radius: OptionalBorderRadiusValue = None + """ + Clip image to have rounded corners. + + Value is of type + [`BorderRadius`](https://flet.dev/docs/reference/types/borderradius). + """ + + color: OptionalColorValue = None + """ + If set, this [color](https://flet.dev/docs/reference/colors) is blended with each + image pixel using `color_blend_mode`. + """ + + color_blend_mode: Optional[BlendMode] = None + """ + Used to combine `color` with the image. + + In terms of the blend mode, color is the source and this image is the destination. + + Value is of type + [`BlendMode`](https://flet.dev/docs/reference/types/blendmode) and defaults to + `BlendMode.COLOR`. + """ + + gapless_playback: OptionalBool = None + """ + Whether to continue showing the old image (`True`), or briefly show nothing + (`False`), when the image provider changes. + + Defaults to `False`. + """ + + semantics_label: OptionalString = None + """ + A semantic description of the image. + + Used to provide a description of the image to TalkBack on Android, and VoiceOver + on iOS. + """ + + exclude_from_semantics: OptionalBool = None + """ + Whether to exclude this image from semantics. + + Defaults to `False`. + """ + + filter_quality: Optional[FilterQuality] = None + """ + The rendering quality of the image. + + Value is of type + [`FilterQuality`](https://flet.dev/docs/reference/types/filterquality) + and defaults to `FilterQuality.MEDIUM`. + """ + + cache_width: OptionalInt = None + """ + The size at which the image should be decoded. + + The image will, however, be rendered to the constraints of the layout regardless + of this parameter. + """ + + cache_height: OptionalInt = None + """ + The size at which the image should be decoded. + + The image will, however, be rendered to the constraints of the layout regardless + of this parameter. + """ + + anti_alias: OptionalBool = None + """ + Whether to paint the image with anti-aliasing. + + Anti-aliasing alleviates the sawtooth artifact when the image is rotated. + """ + diff --git a/sdk/python/packages/flet/src/flet/controls/core/interactive_viewer.py b/sdk/python/packages/flet/src/flet/controls/core/interactive_viewer.py new file mode 100644 index 000000000..b7afe225f --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/interactive_viewer.py @@ -0,0 +1,194 @@ +import asyncio +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.alignment import Alignment +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalEventHandler +from flet.controls.duration import OptionalDurationValue +from flet.controls.events import ( + ScaleEndEvent, + ScaleStartEvent, + ScaleUpdateEvent, +) +from flet.controls.margin import MarginValue +from flet.controls.types import ClipBehavior, Number + +__all__ = ["InteractiveViewer"] + + +@control("InteractiveViewer") +class InteractiveViewer(ConstrainedControl, AdaptiveControl): + """ + InteractiveViewer allows users to pan, zoom, and rotate content. + + Online docs: https://flet.dev/docs/controls/interactiveviewer + """ + + content: Control + """ + The `Control` to be transformed by the `InteractiveViewer`. + """ + + pan_enabled: bool = True + """ + Whether panning is enabled. + + Value is of type `bool` and defaults to `True`. + """ + + scale_enabled: bool = True + """ + Whether scaling is enabled. + + Value is of type `bool` and defaults to `True`. + """ + + trackpad_scroll_causes_scale: bool = False + """ + Whether scrolling up/down on a trackpad should cause scaling instead of panning. + + Value is of type `bool` and defaults to `False`. + """ + + constrained: bool = True + """ + Whether the normal size constraints at this point in the widget tree are applied + to the child. + """ + + max_scale: Number = 2.5 + """ + The maximum allowed scale. Must be greater than or equal to `min_scale`. + + Value is of type `float` and defaults to `2.5`. + """ + + min_scale: Number = 0.8 + """ + The minimum allowed scale. Must be greater than `0` and less than or equal to + `max_scale`. + + Value is of type `float` and defaults to `0.8`. + """ + + interaction_end_friction_coefficient: Number = 0.0000135 + """ + Changes the deceleration behavior after a gesture. Must be greater than `0`. + + Value is of type `float` and defaults to `0.0000135`. + """ + + scale_factor: Number = 200 + """ + The amount of scale to be performed per pointer scroll. + + Value is of type `float` and defaults to `200.0`. + """ + + clip_behavior: ClipBehavior = ClipBehavior.HARD_EDGE + """ + How to clip the `content`. + + Value is of type + [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) and defaults + to `ClipBehavior.HARD_EDGE`. + """ + + alignment: Optional[Alignment] = None + """ + Alignment of the `content` within. + + Value is of type + [`Alignment`](https://flet.dev/docs/reference/types/alignment). + """ + + boundary_margin: MarginValue = 0 + """ + A margin for the visible boundaries of the `content`. + + Value is of type + [`Margin`](https://flet.dev/docs/reference/types/margin). + """ + + interaction_update_interval: int = 200 + """ + The interval (in milliseconds) at which the `on_interaction_update` event is fired. + + Value is of type `int` and defaults to `200`. + """ + + on_interaction_start: OptionalEventHandler[ScaleStartEvent["InteractiveViewer"]] = ( + None + ) + """ + Fires when the user begins a pan or scale gesture. + + Event handler argument is of type + [`ScaleStartEvent`](https://flet.dev/docs/reference/types/scalestartevent). + """ + + on_interaction_update: OptionalEventHandler[ + ScaleUpdateEvent["InteractiveViewer"] + ] = None + """ + Fires when the user updates a pan or scale gesture. + + Event handler argument is of type + [`ScaleUpdateEvent`](https://flet.dev/docs/reference/types/scaleupdateevent). + """ + + on_interaction_end: OptionalEventHandler[ScaleEndEvent["InteractiveViewer"]] = None + """ + Fires when the user ends a pan or scale gesture. + + Event handler argument is of type + [`ScaleEndEvent`](https://flet.dev/docs/reference/types/scaleendevent). + """ + + def before_update(self): + super().before_update() + assert self.content.visible, "content must be visible" + assert self.min_scale > 0, "min_scale must be greater than 0" + assert self.max_scale > 0, "max_scale must be greater than 0" + assert ( + self.max_scale >= self.min_scale + ), "max_scale must be greather than or equal to min_scale" + assert ( + self.interaction_end_friction_coefficient is None + or self.interaction_end_friction_coefficient > 0 + ), "interaction_end_friction_coefficient must be greater than 0" + + def reset(self, animation_duration: OptionalDurationValue = None): + asyncio.create_task(self.reset_async(animation_duration)) + + async def reset_async(self, animation_duration: OptionalDurationValue = None): + await self._invoke_method_async( + "reset", arguments={"animation_duration": animation_duration} + ) + + def save_state(self): + asyncio.create_task(self.save_state_async()) + + async def save_state_async(self): + await self._invoke_method_async("save_state") + + def restore_state(self): + asyncio.create_task(self.restore_state_async()) + + async def restore_state_async(self): + await self._invoke_method_async("restore_state") + + def zoom(self, factor: Number): + asyncio.create_task(self.zoom_async(factor)) + + async def zoom_async(self, factor: Number): + await self._invoke_method_async("zoom", arguments={"factor": factor}) + + def pan(self, dx: Number, dy: Number = 0, dz: Number = 0): + asyncio.create_task(self.pan_async(dx, dy, dz)) + + async def pan_async(self, dx: Number, dy: Number = 0, dz: Number = 0): + await self._invoke_method_async("pan", arguments={"dx": dx, "dy": dy, "dz": dz}) diff --git a/sdk/python/packages/flet/src/flet/controls/core/list_view.py b/sdk/python/packages/flet/src/flet/controls/core/list_view.py new file mode 100644 index 000000000..a7b617d76 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/list_view.py @@ -0,0 +1,119 @@ +from dataclasses import field +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.padding import OptionalPaddingValue +from flet.controls.scrollable_control import ScrollableControl +from flet.controls.types import ClipBehavior, Number, OptionalNumber + +__all__ = ["ListView"] + + +@control("ListView") +class ListView(ConstrainedControl, ScrollableControl, AdaptiveControl): + """ + A scrollable list of controls arranged linearly. + + ListView is the most commonly used scrolling control. It displays its children one + after another in the scroll direction. In the cross axis, the children are required + to fill the ListView. + + Online docs: https://flet.dev/docs/controls/listview + """ + + controls: list[Control] = field(default_factory=list) + """ + A list of `Control`s to display inside ListView. + """ + + horizontal: bool = False + """ + `True` to layout ListView items horizontally. + """ + + reverse: bool = False + """ + Whether the scroll view scrolls in the reading direction. + + For example, if the reading direction is left-to-right and `horizontal` is `True`, + then the scroll view scrolls from left to right when `reverse` is `False` + and from right to left when `reverse` is `True`. + + Similarly, if `horizontal` is `False`, then the scroll view scrolls from top + to bottom when `reverse` is `False` and from bottom to top when `reverse` is `True`. + + Defaults to `False`. + """ + + spacing: Number = 0 + """ + The height of Divider between ListView items. + + No spacing between items if not specified. + """ + + item_extent: OptionalNumber = None + """ + A fixed height or width (for `horizontal` ListView) of an item to optimize + rendering. + """ + + first_item_prototype: bool = False + """ + `True` if the dimensions of the first item should be used as a "prototype" for all + other items, i.e. their height or width will be the same as the first item. + + Defaults to `False`. + """ + + divider_thickness: Number = 0 + """ + If greater than `0` then Divider is used as a spacing between list view items. + """ + + padding: OptionalPaddingValue = None + """ + The amount of space by which to inset the children. + + Value is of type + [`Padding`](https://flet.dev/docs/reference/types/padding). + """ + + clip_behavior: ClipBehavior = ClipBehavior.HARD_EDGE + """ + The content will be clipped (or not) according to this option. + + Value is of type + [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) and defaults + to `ClipBehavior.HARD_EDGE`. + """ + + semantic_child_count: Optional[int] = None + """ + The number of children that will contribute semantic information. + """ + + cache_extent: OptionalNumber = None + """ + Items that fall in the cache area (before or after the visible area that are about + to become visible when the user scrolls) are laid out even though they are not + yet visible on screen. + + The `cache_extent` describes how many pixels the cache area extends before the + leading edge and after the trailing edge of the viewport. + + The total extent covered is: + `cache_extent` before + main axis extent + `cache_extent` after. + """ + + build_controls_on_demand: bool = True + """ + Whether the `controls` should be built lazily/on-demand. + + This is particularly useful when dealing with a large number of controls. + + Defaults to `True`. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/core/markdown.py b/sdk/python/packages/flet/src/flet/controls/core/markdown.py new file mode 100644 index 000000000..98265f3f4 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/markdown.py @@ -0,0 +1,353 @@ +from dataclasses import dataclass +from enum import Enum +from typing import Optional, Union + +from flet.controls.base_control import control +from flet.controls.box import BoxDecoration +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import ( + OptionalControlEventHandler, + OptionalEventHandler, +) +from flet.controls.core.text import TextSelectionChangeEvent +from flet.controls.padding import OptionalPaddingValue +from flet.controls.text_style import OptionalTextStyle +from flet.controls.types import ( + MainAxisAlignment, + OptionalNumber, + TextAlign, + UrlTarget, +) + +__all__ = [ + "Markdown", + "MarkdownExtensionSet", + "MarkdownStyleSheet", + "MarkdownCodeTheme", + "MarkdownCustomCodeTheme", +] + + +class MarkdownExtensionSet(Enum): + NONE = "none" + COMMON_MARK = "commonMark" + GITHUB_WEB = "gitHubWeb" + GITHUB_FLAVORED = "gitHubFlavored" + + +@dataclass +class MarkdownStyleSheet: + a_text_style: OptionalTextStyle = None + p_text_style: OptionalTextStyle = None + p_padding: OptionalPaddingValue = None + code_text_style: OptionalTextStyle = None + h1_text_style: OptionalTextStyle = None + h1_padding: OptionalPaddingValue = None + h2_text_style: OptionalTextStyle = None + h2_padding: OptionalPaddingValue = None + h3_text_style: OptionalTextStyle = None + h3_padding: OptionalPaddingValue = None + h4_text_style: OptionalTextStyle = None + h4_padding: OptionalPaddingValue = None + h5_text_style: OptionalTextStyle = None + h5_padding: OptionalPaddingValue = None + h6_text_style: OptionalTextStyle = None + h6_padding: OptionalPaddingValue = None + em_text_style: OptionalTextStyle = None + strong_text_style: OptionalTextStyle = None + del_text_style: OptionalTextStyle = None + blockquote_text_style: OptionalTextStyle = None + img_text_style: OptionalTextStyle = None + checkbox_text_style: OptionalTextStyle = None + block_spacing: OptionalNumber = None + list_indent: OptionalNumber = None + list_bullet_text_style: OptionalTextStyle = None + list_bullet_padding: OptionalPaddingValue = None + table_head_text_style: OptionalTextStyle = None + table_body_text_style: OptionalTextStyle = None + table_head_text_align: Optional[TextAlign] = None + table_padding: OptionalPaddingValue = None + table_cells_padding: OptionalPaddingValue = None + blockquote_padding: OptionalPaddingValue = None + table_cells_decoration: Optional[BoxDecoration] = None + blockquote_decoration: Optional[BoxDecoration] = None + codeblock_padding: OptionalPaddingValue = None + codeblock_decoration: Optional[BoxDecoration] = None + horizontal_rule_decoration: Optional[BoxDecoration] = None + blockquote_alignment: Optional[MainAxisAlignment] = None + codeblock_alignment: Optional[MainAxisAlignment] = None + h1_alignment: Optional[MainAxisAlignment] = None + h2_alignment: Optional[MainAxisAlignment] = None + h3_alignment: Optional[MainAxisAlignment] = None + h4_alignment: Optional[MainAxisAlignment] = None + h5_alignment: Optional[MainAxisAlignment] = None + h6_alignment: Optional[MainAxisAlignment] = None + text_alignment: Optional[MainAxisAlignment] = None + ordered_list_alignment: Optional[MainAxisAlignment] = None + unordered_list_alignment: Optional[MainAxisAlignment] = None + + +class MarkdownCodeTheme(Enum): + A11Y_DARK = "a11y-dark" + A11Y_LIGHT = "a11y-light" + AGATE = "agate" + AN_OLD_HOPE = "an-old-hope" + ANDROID_STUDIO = "androidstudio" + ARDUINO_LIGHT = "arduino-light" + ARTA = "arta" + ASCETIC = "ascetic" + ATELIER_CAVE_DARK = "atelier-cave-dark" + ATELIER_CAVE_LIGHT = "atelier-cave-light" + ATELIER_DUNE_DARK = "atelier-dune-dark" + ATELIER_DUNE_LIGHT = "atelier-dune-light" + ATELIER_ESTUARY_DARK = "atelier-estuary-dark" + ATELIER_ESTUARY_LIGHT = "atelier-estuary-light" + ATELIER_FOREST_DARK = "atelier-forest-dark" + ATELIER_FOREST_LIGHT = "atelier-forest-light" + ATELIER_HEATH_DARK = "atelier-heath-dark" + ATELIER_HEATH_LIGHT = "atelier-heath-light" + ATELIER_LAKESIDE_DARK = "atelier-lakeside-dark" + ATELIER_LAKESIDE_LIGHT = "atelier-lakeside-light" + ATELIER_PLATEAU_DARK = "atelier-plateau-dark" + ATELIER_PLATEAU_LIGHT = "atelier-plateau-light" + ATELIER_SAVANNA_DARK = "atelier-savanna-dark" + ATELIER_SAVANNA_LIGHT = "atelier-savanna-light" + ATELIER_SEASIDE_DARK = "atelier-seaside-dark" + ATELIER_SEASIDE_LIGHT = "atelier-seaside-light" + ATELIER_SULPHURPOOL_DARK = "atelier-sulphurpool-dark" + ATELIER_SULPHURPOOL_LIGHT = "atelier-sulphurpool-light" + ATOM_ONE_DARK_REASONABLE = "atom-one-dark-reasonable" + ATOM_ONE_DARK = "atom-one-dark" + ATOM_ONE_LIGHT = "atom-one-light" + BROWN_PAPER = "brown-paper" + CODEPEN_EMBED = "codepen-embed" + COLOR_BREWER = "color-brewer" + DARCULA = "darcula" + DARK = "dark" + DEFAULT = "default" + DOCCO = "docco" + DRAGULA = "dracula" + FAR = "far" + FOUNDATION = "foundation" + GITHUB_GIST = "github-gist" + GITHUB = "github" + GML = "gml" + GOOGLE_CODE = "googlecode" + GRADIENT_DARK = "gradient-dark" + GRAYSCALE = "grayscale" + GRUVBOX_DARK = "gruvbox-dark" + GRUVBOX_LIGHT = "gruvbox-light" + HOPSCOTCH = "hopscotch" + HYBRID = "hybrid" + IDEA = "idea" + IR_BLACK = "ir-black" + ISBL_EDITOR_DARK = "isbl-editor-dark" + ISBL_EDITOR_LIGHT = "isbl-editor-light" + KIMBIE_DARK = "kimbie.dark" + KIMBIE_LIGHT = "kimbie.light" + LIGHTFAIR = "lightfair" + MAGULA = "magula" + MONO_BLUE = "mono-blue" + MONOKAI_SUBLIME = "monokai-sublime" + MONOKAI = "monokai" + NIGHT_OWL = "night-owl" + NORD = "nord" + OBSIDIAN = "obsidian" + OCEAN = "ocean" + PARAISEO_DARK = "paraiso-dark" + PARAISEO_LIGHT = "paraiso-light" + POJOAQUE = "pojoaque" + PURE_BASIC = "purebasic" + QT_CREATOR_DARK = "qtcreator_dark" + QT_CREATOR_LIGHT = "qtcreator_light" + RAILSCASTS = "railscasts" + RAINBOW = "rainbow" + ROUTEROS = "routeros" + SCHOOL_BOOK = "school-book" + SHADES_OF_PURPLE = "shades-of-purple" + SOLARIZED_DARK = "solarized-dark" + SOLARIZED_LIGHT = "solarized-light" + SUNBURST = "sunburst" + TOMORROW_NIGHT_BLUE = "tomorrow-night-blue" + TOMORROW_NIGHT_BRIGHT = "tomorrow-night-bright" + TOMORROW_NIGHT_EIGHTIES = "tomorrow-night-eighties" + TOMORROW_NIGHT = "tomorrow-night" + TOMORROW = "tomorrow" + VS = "vs" + VS2015 = "vs2015" + XCODE = "xcode" + XT256 = "xt256" + ZENBURN = "zenburn" + + +@dataclass +class MarkdownCustomCodeTheme: + addition: OptionalTextStyle = None + attr: OptionalTextStyle = None + attribute: OptionalTextStyle = None + built_in: OptionalTextStyle = None + builtin_name: OptionalTextStyle = None + bullet: OptionalTextStyle = None + class_name: OptionalTextStyle = None + code: OptionalTextStyle = None + comment: OptionalTextStyle = None + deletion: OptionalTextStyle = None + doctag: OptionalTextStyle = None + emphasis: OptionalTextStyle = None + formula: OptionalTextStyle = None + function: OptionalTextStyle = None + keyword: OptionalTextStyle = None + link: OptionalTextStyle = None + link_label: OptionalTextStyle = None + literal: OptionalTextStyle = None + meta: OptionalTextStyle = None + meta_keyword: OptionalTextStyle = None + meta_string: OptionalTextStyle = None + name: OptionalTextStyle = None + number: OptionalTextStyle = None + operator: OptionalTextStyle = None + params: OptionalTextStyle = None + pattern_match: OptionalTextStyle = None + quote: OptionalTextStyle = None + regexp: OptionalTextStyle = None + root: OptionalTextStyle = None + section: OptionalTextStyle = None + selector_attr: OptionalTextStyle = None + selector_class: OptionalTextStyle = None + selector_id: OptionalTextStyle = None + selector_pseudo: OptionalTextStyle = None + selector_tag: OptionalTextStyle = None + string: OptionalTextStyle = None + strong: OptionalTextStyle = None + stronge: OptionalTextStyle = None + subst: OptionalTextStyle = None + subtr: OptionalTextStyle = None + symbol: OptionalTextStyle = None + tag: OptionalTextStyle = None + template_tag: OptionalTextStyle = None + template_variable: OptionalTextStyle = None + title: OptionalTextStyle = None + type: OptionalTextStyle = None + variable: OptionalTextStyle = None + + +@control("Markdown") +class Markdown(ConstrainedControl): + """ + Control for rendering text in markdown format. + + Online docs: https://flet.dev/docs/controls/markdown + """ + + value: str = "" + """ + Markdown content to render. + """ + + selectable: bool = False + """ + Whether rendered text is selectable or not. + """ + + extension_set: MarkdownExtensionSet = MarkdownExtensionSet.NONE + """ + The extensions to use when rendering the markdown content. + + Value is of type + [`MarkdownExtensionSet`](https://flet.dev/docs/reference/types/markdownextensionset) + and defaults to `MarkdownExtensionSet.NONE`. + """ + + code_theme: Optional[Union[MarkdownCodeTheme, MarkdownCustomCodeTheme]] = None + """ + A syntax highlighting theme for code blocks. + + Value is of type + [`MarkdownCodeTheme`](https://flet.dev/docs/reference/types/markdowncodetheme) + and defaults to `MarkdownCodeTheme.GITHUB`. + """ + + auto_follow_links: bool = False + """ + Automatically open URLs in the document. + + Default is `False`. If registered, `on_tap_link` event is fired after that. + """ + + shrink_wrap: bool = True + """ + Whether the extent of the scroll view in the scroll direction should be determined + by the contents being viewed. + + Value is of type `bool` and defaults to `True`. + """ + + fit_content: bool = True + """ + Whether to allow the widget to fit the child content. + + Value is of type `bool` and defaults to `True`. + """ + + soft_line_break: bool = False + """ + The soft line break is used to identify the spaces at the end of a line of text + and the leading spaces in the immediately following the line of text. + + Value is of type `bool` and defaults to `False`. + """ + + auto_follow_links_target: Optional[UrlTarget] = None + """ + Where to open URL in the web mode. + + Value is of type + [`UrlTarget`](https://flet.dev/docs/reference/types/urltarget) + and defaults to `UrlTarget.SELF`. + """ + + img_error_content: Optional[Control] = None + """ + The `Control` to display when an image fails to load. + """ + + code_style_sheet: Optional[MarkdownStyleSheet] = None + """ + The styles to use when displaying the code blocks. + + Value is of type + [`MarkdownStyleSheet`](https://flet.dev/docs/reference/types/markdownstylesheet). + """ + + md_style_sheet: Optional[MarkdownStyleSheet] = None + """ + The styles to use when displaying the markdown. + + Value is of type + [`MarkdownStyleSheet`](https://flet.dev/docs/reference/types/markdownstylesheet). + """ + + on_tap_text: OptionalControlEventHandler["Markdown"] = None + """ + Fires when some text is clicked/tapped. + """ + + on_selection_change: OptionalEventHandler[TextSelectionChangeEvent[ + "Markdown"]] = None + """ + Fires when the text selection changes. + + Event handler argument is of type + [`MarkdownSelectionChangeEvent`](https://flet.dev/docs/reference/types/markdownselectionchangeevent). + """ + + on_tap_link: OptionalControlEventHandler["Markdown"] = None + """ + Fires when a link within Markdown document is clicked/tapped. + + `data` property of event contains URL. + + Example: + https://github.com/flet-dev/examples/blob/main/python/controls/information-displays/markdown/markdown-event-example.py + """ diff --git a/sdk/python/packages/flet/src/flet/controls/core/merge_semantics.py b/sdk/python/packages/flet/src/flet/controls/core/merge_semantics.py new file mode 100644 index 000000000..d9227b738 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/merge_semantics.py @@ -0,0 +1,26 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control import Control + +__all__ = ["MergeSemantics"] + + +@control("MergeSemantics") +class MergeSemantics(Control): + """ + A control that merges the semantics of its descendants. + + Causes all the semantics of the subtree rooted at this node to be merged into one + node in the semantics tree. + + Used by accessibility tools, search engines, and other semantic analysis software + to determine the meaning of the application. + + Online docs: https://flet.dev/docs/controls/mergesemantics + """ + + content: Optional[Control] = None + """ + The `Control` to annotate. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/core/pagelet.py b/sdk/python/packages/flet/src/flet/controls/core/pagelet.py new file mode 100644 index 000000000..d84306b5b --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/pagelet.py @@ -0,0 +1,131 @@ +from typing import Optional, Union + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control, OptionalControl +from flet.controls.cupertino.cupertino_app_bar import CupertinoAppBar +from flet.controls.cupertino.cupertino_navigation_bar import CupertinoNavigationBar +from flet.controls.material.app_bar import AppBar +from flet.controls.material.bottom_app_bar import BottomAppBar +from flet.controls.material.navigation_bar import NavigationBar +from flet.controls.material.navigation_drawer import NavigationDrawer +from flet.controls.transform import OffsetValue +from flet.controls.types import FloatingActionButtonLocation, OptionalColorValue + +__all__ = ["Pagelet"] + + +@control("Pagelet") +class Pagelet(ConstrainedControl, AdaptiveControl): + """ + Pagelet implements the basic Material Design visual layout structure. + + Use it for projects that require "page within a page" layouts with its own AppBar, + BottomBar, Drawer, such as demos and galleries. + + Online docs: https://flet.dev/docs/controls/pagelet + """ + + content: Control + """ + A child Control contained by the Pagelet. + + The control in the content of the Pagelet is positioned at the top-left of the + available space between the app bar and the bottom of the Pagelet. + """ + + appbar: Optional[Union[AppBar, CupertinoAppBar]] = None + """ + An [`AppBar`](https://flet.dev/docs/controls/appbar) control to display at the top + of the Pagelet. + """ + + navigation_bar: Optional[Union[NavigationBar, CupertinoNavigationBar]] = None + """ + [`NavigationBar`](https://flet.dev/docs/controls/navigationbar) control to display + at the bottom of the page. + + If both [`bottom_appbar`](https://flet.dev/docs/controls/pagelet#bottom_appbar) and + [`navigation_bar`](https://flet.dev/docs/controls/pagelet#navigation_bar) + properties are provided, `NavigationBar` will be displayed. + """ + + bottom_appbar: Optional[BottomAppBar] = None + """ + [`BottomAppBar`](https://flet.dev/docs/controls/bottomappbar) control to display at + the bottom of the Pagelet. + + If both [`bottom_appbar`](https://flet.dev/docs/controls/pagelet#bottom_appbar) and + [`navigation_bar`](https://flet.dev/docs/controls/pagelet#navigation_bar) + properties are provided, `NavigationBar` will be displayed. + """ + + bottom_sheet: OptionalControl = None + """ + The persistent bottom sheet to show information that supplements the primary + content of the Pagelet. Can be any control. + """ + + drawer: Optional[NavigationDrawer] = None + """ + A [`NavigationDrawer`](https://flet.dev/docs/controls/navigationdrawer) control to + display as a panel sliding from the start edge of the page. + """ + + end_drawer: Optional[NavigationDrawer] = None + """ + A [`NavigationDrawer`](https://flet.dev/docs/controls/navigationdrawer) control to + display as a panel sliding from the end edge of the page. + """ + + floating_action_button: Optional[Control] = None + """ + A [`FloatingActionButton`](https://flet.dev/docs/controls/floatingactionbutton) + control to display on top of Pagelet content. + """ + + floating_action_button_location: Optional[ + Union[FloatingActionButtonLocation, OffsetValue] + ] = None + """ + Defines a position for the `FloatingActionButton`. + + Value can be of type `OffsetValue` or + [`FloatingActionButtonLocation`](https://flet.dev/docs/reference/types/floatingactionbuttonlocation). + Defaults to `FloatingActionButtonLocation.END_FLOAT`. + """ + + bgcolor: OptionalColorValue = None + """ + Background [color](https://flet.dev/docs/reference/colors) of the Pagelet. + """ + + def before_update(self): + super().before_update() + assert self.content.visible, "content must be visible" + + # todo: deprecate show_* in favor of a open/close methods, or page.open/close + # Drawer + # + def show_drawer(self, drawer: NavigationDrawer): + self.drawer = drawer + self.drawer.open = True + self.update() + + def close_drawer(self): + if self.drawer is not None: + self.drawer.open = False + self.update() + + # End_drawer + # + def show_end_drawer(self, end_drawer: NavigationDrawer): + self.end_drawer = end_drawer + self.end_drawer.open = True + self.update() + + def close_end_drawer(self): + if self.end_drawer is not None: + self.end_drawer.open = False + self.update() diff --git a/sdk/python/packages/flet/src/flet/controls/core/placeholder.py b/sdk/python/packages/flet/src/flet/controls/core/placeholder.py new file mode 100644 index 000000000..c406ad1ff --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/placeholder.py @@ -0,0 +1,50 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.colors import Colors +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.types import ColorValue, Number, OptionalNumber + +__all__ = ["Placeholder"] + + +@control("Placeholder") +class Placeholder(ConstrainedControl): + """ + A placeholder box. + + Online docs: https://flet.dev/docs/controls/placeholder + """ + + content: Optional[Control] = None + """ + An optional `Control` to display inside the placeholder. + """ + + color: ColorValue = Colors.BLUE_GREY_700 + """ + The [color](https://flet.dev/docs/reference/colors) of the placeholder box. + """ + + fallback_height: Number = 400.0 + """ + The height to use when the placeholder is in a situation with an unbounded height. + + Value is of `float` and defaults to `400.0`. + """ + + fallback_width: Number = 400.0 + """ + The width to use when the placeholder is in a situation with an unbounded width. + + Value is of `float` and defaults to `400.0`. + """ + + stroke_width: OptionalNumber = 2.0 + """ + The width of the lines in the placeholder box. + + Value is of `float` and defaults to `2.0`. + """ + diff --git a/sdk/python/packages/flet/src/flet/controls/core/reorderable_draggable.py b/sdk/python/packages/flet/src/flet/controls/core/reorderable_draggable.py new file mode 100644 index 000000000..bc4878790 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/reorderable_draggable.py @@ -0,0 +1,23 @@ +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control + + +@control("ReorderableDraggable") +class ReorderableDraggable(ConstrainedControl, AdaptiveControl): + """ + TBD + """ + index: int + """ + TBD + """ + content: Control + """ + TBD + """ + + def before_update(self): + super().before_update() + assert self.content.visible, "content must be visible" diff --git a/sdk/python/packages/flet/src/flet/controls/core/responsive_row.py b/sdk/python/packages/flet/src/flet/controls/core/responsive_row.py new file mode 100644 index 000000000..d833ed42c --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/responsive_row.py @@ -0,0 +1,95 @@ +from dataclasses import field +from typing import Union + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.types import ( + CrossAxisAlignment, + MainAxisAlignment, + Number, + ResponsiveNumber, + ResponsiveRowBreakpoint, +) + +__all__ = ["ResponsiveRow", "ResponsiveRowBreakpoint", "ResponsiveNumber"] + + +@control("ResponsiveRow") +class ResponsiveRow(ConstrainedControl, AdaptiveControl): + """ + ResponsiveRow allows aligning child controls to virtual columns. By default, a + virtual grid has 12 columns, but that can be customized with + `ResponsiveRow.columns` property. + + Similar to `expand` property, every control now has `col` property which allows + specifying how many columns a control should span. + + Online docs: https://flet.dev/docs/controls/responsiverow + """ + + controls: list[Control] = field(default_factory=list) + """ + A list of Controls to display inside the ResponsiveRow. + """ + + columns: ResponsiveNumber = 12 + """ + The number of virtual columns to layout children. + + Defaults to `12`. + """ + + alignment: MainAxisAlignment = MainAxisAlignment.START + """ + How the child Controls should be placed horizontally. + + Value is of type + [`MainAxisAlignment`](https://flet.dev/docs/reference/types/mainaxisalignment) + and defaults to `MainAxisAlignment.START`. + """ + + vertical_alignment: CrossAxisAlignment = CrossAxisAlignment.START + """ + How the child Controls should be placed vertically. + + Value is of type + [`CrossAxisAlignment`](https://flet.dev/docs/reference/types/crossaxisalignment) + and defaults to `CrossAxisAlignment.START`. + """ + + spacing: ResponsiveNumber = 10 + """ + Spacing between controls in a row in virtual pixels. + + It is applied only when `alignment` is set to `MainAxisAlignment.START`, + `MainAxisAlignment.END` or `MainAxisAlignment.CENTER`. + + Defaults to `10`. + """ + + run_spacing: ResponsiveNumber = 10 + """ + Spacing between runs when row content is wrapped on multiple lines. + + Defaults to `10`. + """ + + breakpoints: dict[Union[ResponsiveRowBreakpoint, str], Number] = field( + default_factory=lambda: { + ResponsiveRowBreakpoint.XS: 0, + ResponsiveRowBreakpoint.SM: 576, + ResponsiveRowBreakpoint.MD: 768, + ResponsiveRowBreakpoint.LG: 992, + ResponsiveRowBreakpoint.XL: 1200, + ResponsiveRowBreakpoint.XXL: 1400, + } + ) + """ + TBD + """ + + def clean(self): + super().clean() + self.controls.clear() diff --git a/sdk/python/packages/flet/src/flet/controls/core/row.py b/sdk/python/packages/flet/src/flet/controls/core/row.py new file mode 100644 index 000000000..6fbab7183 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/row.py @@ -0,0 +1,83 @@ +from dataclasses import field + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.scrollable_control import ScrollableControl +from flet.controls.types import CrossAxisAlignment, MainAxisAlignment, Number + +__all__ = ["Row"] + + +@control("Row") +class Row(ConstrainedControl, ScrollableControl, AdaptiveControl): + """ + A control that displays its children in a horizontal array. + + To cause a child control to expand and fill the available horizontal space, set + its `expand` property. + + Online docs: https://flet.dev/docs/controls/row + """ + + controls: list[Control] = field(default_factory=list) + """ + A list of Controls to display inside the Row. + """ + + alignment: MainAxisAlignment = MainAxisAlignment.START + """ + How the child Controls should be placed horizontally. + + Value is of type + [`MainAxisAlignment`](https://flet.dev/docs/reference/types/mainaxisalignment) + and defaults to `MainAxisAlignment.START`. + """ + + vertical_alignment: CrossAxisAlignment = CrossAxisAlignment.CENTER + """ + How the child Controls should be placed vertically. + + Value is of type + [`CrossAxisAlignment`](https://flet.dev/docs/reference/types/crossaxisalignment) + and defaults to `CrossAxisAlignment.START`. + """ + + spacing: Number = 10 + """ + Spacing between controls in a row. + + Default value is `10` virtual pixels. Spacing is applied only when `alignment` is + set to `MainAxisAlignment.START`, `MainAxisAlignment.END` or + `MainAxisAlignment.CENTER`. + """ + + tight: bool = False + """ + Specifies how much space should be occupied horizontally. + + Defaults to `False`, meaning all space is allocated to children. + """ + + wrap: bool = False + """ + When set to `True` the Row will put child controls into additional rows (runs) if + they don't fit a single row. + """ + + run_spacing: Number = 10 + """ + Spacing between runs when `wrap=True`. + + Defaults to `10`. + """ + + run_alignment: MainAxisAlignment = MainAxisAlignment.START + """ + How the runs should be placed in the cross-axis when `wrap=True`. + + Value is of type + [`MainAxisAlignment`](https://flet.dev/docs/reference/types/mainaxisalignment) + and defaults to `MainAxisAlignment.START`. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/core/safe_area.py b/sdk/python/packages/flet/src/flet/controls/core/safe_area.py new file mode 100644 index 000000000..643854766 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/safe_area.py @@ -0,0 +1,80 @@ +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.padding import PaddingValue + +__all__ = ["SafeArea"] + + +@control("SafeArea") +class SafeArea(ConstrainedControl, AdaptiveControl): + """ + A control that insets its `content` by sufficient padding to avoid intrusions by + the operating system. + + For example, this will indent the `content` by enough to avoid the status bar at + the top of the screen. + + It will also indent the `content` by the amount necessary to avoid The Notch on the + iPhone X, or other similar creative physical features of the display. + + When a `minimum_padding` is specified, the greater of the minimum padding or the + safe area padding will be applied. + """ + + content: Control + """ + A `Control` to display inside safe area. + """ + + avoid_intrusions_left: bool = True + """ + Whether to avoid system intrusions on the left. + + Defaults to `True`. + """ + + avoid_intrusions_top: bool = True + """ + Whether to avoid system intrusions at the top of the screen, typically the system + status bar. + + Defaults to `True`. + """ + + avoid_intrusions_right: bool = True + """ + Whether to avoid system intrusions on the right. + + Defaults to `True`. + """ + + avoid_intrusions_bottom: bool = True + """ + Whether to avoid system intrusions on the bottom side of the screen. + + Defaults to `True`. + """ + + maintain_bottom_view_padding: bool = False + """ + Specifies whether the `SafeArea` should maintain the bottom + `MediaQueryData.viewPadding` instead of the bottom `MediaQueryData.padding`. + + Defaults to `False`. + + This avoids layout shifts caused by keyboard overlays, useful when flexible + controls are used. + """ + + minimum_padding: PaddingValue = 0 + """ + This minimum padding to apply. + + The greater of the minimum insets and the media padding will be applied. + """ + + def before_update(self): + super().before_update() + assert self.content.visible, "content must be visible" diff --git a/sdk/python/packages/flet/src/flet/controls/core/semantics.py b/sdk/python/packages/flet/src/flet/controls/core/semantics.py new file mode 100644 index 000000000..78d84ddf5 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/semantics.py @@ -0,0 +1,291 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.material.badge import BadgeValue +from flet.controls.types import OptionalNumber + +__all__ = ["Semantics"] + + +@control("Semantics") +class Semantics(Control): + """ + A control that annotates the control tree with a description of the meaning of the + widgets. + + Used by accessibility tools, search engines, and other semantic analysis software + to determine the meaning of the application. + + Online docs: https://flet.dev/docs/controls/semantics + """ + + content: Optional[Control] = None + """ + The `Control` to annotate. + """ + + label: Optional[str] = None + """ + A textual description of the `content` control. + """ + + expanded: Optional[bool] = None + """ + Whether this subtree represents something that can be in an "expanded" or + "collapsed" state. + """ + + hidden: Optional[bool] = None + """ + Whether this subtree is currently hidden. + """ + + selected: Optional[bool] = None + """ + Whether this subtree represents something that can be in a selected or unselected + state, and what its current state is. + """ + + button: Optional[bool] = None + """ + Whether this subtree represents a button. + """ + + obscured: Optional[bool] = None + """ + Whether `value` should be obscured. + """ + + multiline: Optional[bool] = None + """ + Whether the `value` is coming from a field that supports multiline text editing. + """ + + focusable: Optional[bool] = None + """ + Whether the node is able to hold input focus. + """ + + read_only: Optional[bool] = None + """ + Whether this subtree is read only. + """ + + focus: Optional[bool] = None + """ + Whether the node currently holds input focus. + """ + + slider: Optional[bool] = None + """ + Whether this subtree represents a slider. + """ + + tooltip: Optional[str] = None + """ + A textual description of the widget's tooltip. + """ + + badge: Optional[BadgeValue] = None + """ + TBD + """ + + toggled: Optional[bool] = None + """ + Whether this subtree represents a toggle switch or similar widget with an "on" + state, and what its current state is. + """ + + max_value_length: OptionalNumber = None + """ + The maximum number of characters that can be entered into an editable text field. + """ + + checked: Optional[bool] = None + """ + Whether this subtree represents a checkbox or similar widget with a "checked" + state, and what its current state is. + """ + + value: Optional[str] = None + """ + A textual description of the `value` of the `content` control. + """ + + increased_value: Optional[str] = None + """ + The value that the semantics node represents when it is increased. + """ + + decreased_value: Optional[str] = None + """ + The value that the semantics node represents when it is decreased. + """ + + hint_text: Optional[str] = None + """ + A brief textual description of the result of an action performed on the `content` + control. + """ + + on_tap_hint_text: Optional[str] = None + """ + TBD + """ + + current_value_length: Optional[int] = None + """ + The current number of characters that have been entered into an editable text + field. + """ + + heading_level: Optional[int] = None + """ + The heading level in the DOM document structure. + """ + + exclude_semantics: bool = False + """ + TBD + """ + + mixed: Optional[bool] = None + """ + Whether this subtree represents a checkbox or similar control with a "half-checked" + state or similar, and whether it is currently in this half-checked state. + """ + + on_long_press_hint_text: Optional[str] = None + """ + TBD + """ + + container: Optional[bool] = None + """ + TBD + """ + + live_region: Optional[bool] = None + """ + Whether this subtree should be considered a live region. + """ + + textfield: Optional[bool] = None + """ + Whether this subtree represents a text field. + """ + + link: Optional[bool] = None + """ + Whether this subtree represents a link. + """ + + header: Optional[bool] = None + """ + Whether this subtree represents a header. + """ + + image: Optional[bool] = None + """ + Whether the node represents an image. + """ + + on_tap: OptionalControlEventHandler["Semantics"] = None + """ + Fires when this control is tapped. + """ + + on_double_tap: OptionalControlEventHandler["Semantics"] = None + """ + TBD + """ + + on_increase: OptionalControlEventHandler["Semantics"] = None + """ + Fires when the value represented by the semantics node is increased. + """ + + on_decrease: OptionalControlEventHandler["Semantics"] = None + """ + Fires when the value represented by the semantics node is decreased. + """ + + on_dismiss: OptionalControlEventHandler["Semantics"] = None + """ + Fires when the node is dismissed. + """ + + on_scroll_left: OptionalControlEventHandler["Semantics"] = None + """ + Fires when a user moves their finger across the screen from right to left. + """ + + on_scroll_right: OptionalControlEventHandler["Semantics"] = None + """ + Fires when a user moves their finger across the screen from left to right. + """ + + on_scroll_up: OptionalControlEventHandler["Semantics"] = None + """ + Fires when a user moves their finger across the screen from bottom to top. + """ + + on_scroll_down: OptionalControlEventHandler["Semantics"] = None + """ + Fires when a user moves their finger across the screen from top to bottom. + """ + + on_copy: OptionalControlEventHandler["Semantics"] = None + """ + Fires when the current selection is copied to the clipboard. + """ + + on_cut: OptionalControlEventHandler["Semantics"] = None + """ + Fires when the current selection is cut to the clipboard. + """ + + on_paste: OptionalControlEventHandler["Semantics"] = None + """ + Fires when the current content of the clipboard is pasted. + """ + + on_long_press: OptionalControlEventHandler["Semantics"] = None + """ + Fires when the node is long-pressed (pressing and holding the screen with the + finger for a few seconds without moving it). + """ + + on_move_cursor_forward_by_character: OptionalControlEventHandler["Semantics"] = None + """ + Fires when the cursor is moved forward by one character. + """ + + on_move_cursor_backward_by_character: OptionalControlEventHandler["Semantics"] = ( + None + ) + """ + Fires when the cursor is moved backward by one character. + """ + + on_did_gain_accessibility_focus: OptionalControlEventHandler["Semantics"] = None + """ + Fires when the node has gained accessibility focus. + """ + + on_did_lose_accessibility_focus: OptionalControlEventHandler["Semantics"] = None + """ + Fires when the node has lost accessibility focus. + """ + + on_set_text: OptionalControlEventHandler["Semantics"] = None + """ + Fires when a user wants to replace the current text in the text field with a new + text. + + Voice access users can trigger this handler by speaking type `` to their + Android devices. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/core/shader_mask.py b/sdk/python/packages/flet/src/flet/controls/core/shader_mask.py new file mode 100644 index 000000000..938548a44 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/shader_mask.py @@ -0,0 +1,48 @@ +from flet.controls.base_control import control +from flet.controls.border_radius import OptionalBorderRadiusValue +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import OptionalControl +from flet.controls.gradients import Gradient +from flet.controls.types import BlendMode + +__all__ = ["ShaderMask"] + + +@control("ShaderMask") +class ShaderMask(ConstrainedControl): + """ + A control that applies a mask generated by a shader to its child. + + For example, ShaderMask can be used to gradually fade out the edge of a child by + using a `LinearGradient` mask. + + Online docs: https://flet.dev/docs/controls/shadermask + """ + + shader: Gradient + """ + Use gradient as a shader. + + Value is of type [`Gradient`](https://flet.dev/docs/reference/types/gradient). + """ + + content: OptionalControl = None + """ + A child `Control` to apply a shader to. + """ + + blend_mode: BlendMode = BlendMode.MODULATE + """ + The blend mode to use when applying the shader to the `content`. + + Value is of type [`BlendMode`](https://flet.dev/docs/reference/types/blendmode) and + defaults to `BlendMode.MODULATE`. + """ + + border_radius: OptionalBorderRadiusValue = None + """ + The radius of the mask. + + Value is of type [`BorderRadius`](https://flet.dev/docs/reference/types/borderradius). + """ + diff --git a/sdk/python/packages/flet/src/flet/controls/core/stack.py b/sdk/python/packages/flet/src/flet/controls/core/stack.py new file mode 100644 index 000000000..320042d13 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/stack.py @@ -0,0 +1,63 @@ +from dataclasses import field +from enum import Enum +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.alignment import Alignment +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.types import ClipBehavior + +__all__ = ["Stack", "StackFit"] + + +class StackFit(Enum): + LOOSE = "loose" + EXPAND = "expand" + PASS_THROUGH = "passThrough" + + +@control("Stack") +class Stack(ConstrainedControl, AdaptiveControl): + """ + A control that positions its children on top of each other. + + This control is useful if you want to overlap several children in a simple way, + for example having some text and an image, overlaid with a gradient and a button + attached to the bottom. + + Stack is also useful if you want to implement implicit animations + (https://flet.dev/docs/guides/python/animations/) that require knowing absolute + position of a target value. + + Online docs: https://flet.dev/docs/controls/stack + """ + + controls: list[Control] = field(default_factory=list) + """ + A list of Controls to display inside the Stack. The last control in the list is + displayed on top. + """ + + clip_behavior: ClipBehavior = ClipBehavior.HARD_EDGE + """ + The content will be clipped (or not) according to this option. + + Value is of type [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) + and defaults to `ClipBehavior.HARD_EDGE`. + """ + + alignment: Optional[Alignment] = None + """ + The alignment of the non-positioned (those that do not specify an alignment - ex + neither top nor bottom - in a particular axis and partially-positioned `controls`. + """ + + fit: StackFit = StackFit.LOOSE + """ + How to size the non-positioned `controls`. + + Value is of type [`StackFit`](https://flet.dev/docs/reference/types/stackfit) and + defaults to `StackFit.LOOSE`. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/core/text.py b/sdk/python/packages/flet/src/flet/controls/core/text.py new file mode 100644 index 000000000..02cf8b55f --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/text.py @@ -0,0 +1,341 @@ +from dataclasses import dataclass +from enum import Enum +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import ( + Event, + EventControlType, + OptionalControlEventHandler, + OptionalEventHandler, +) +from flet.controls.core.text_span import TextSpan +from flet.controls.text_style import TextOverflow, TextStyle, TextThemeStyle +from flet.controls.types import ( + FontWeight, + OptionalColorValue, + OptionalNumber, + TextAlign, +) + +__all__ = [ + "Text", + "TextSelection", + "TextSelectionChangeEvent", + "TextSelectionChangeCause", + "TextAffinity", +] + + +class TextAffinity(Enum): + """ + Defines the permissions which can be checked and requested. + """ + + UPSTREAM = "upstream" + """ + The position has affinity for the downstream side of the text position, i.e. in the + direction of the end of the string. + """ + DOWNSTREAM = "downstream" + """ + The position has affinity for the upstream side of the text position, i.e. in the + direction of the beginning of the string. + """ + + +@dataclass +class TextSelection: + """ + A range of text that represents a selection. + """ + + start: Optional[int] = None + """ + The index of the first character in the range. + """ + + end: Optional[int] = None + """ + The next index after the characters in this range. + """ + + selection: Optional[str] = None + """ + The text string that is selected. + """ + + base_offset: Optional[int] = None + """ + The offset at which the selection originates. + """ + + extent_offset: Optional[int] = None + """ + The offset at which the selection terminates. + """ + + affinity: Optional["TextAffinity"] = None + """ + If the text range is collapsed and has more than one visual location (e.g., occurs + at a line break), which of the two locations to use when painting the caret. + + Value is of type [`TextAffinity`](https://flet.dev/docs/reference/types/textaffinity). + """ + + directional: Optional[bool] = None + """ + Whether this selection has disambiguated its base and extent. + """ + + collapsed: Optional[bool] = None + """ + Whether this range is empty (but still potentially placed inside the text). + """ + + valid: Optional[bool] = None + """ + Whether this range represents a valid position in the text. + """ + + normalized: Optional[bool] = None + """ + Whether the start of this range precedes the end. + """ + + +class TextSelectionChangeCause(Enum): + """ + Indicates what triggered the change in selected text. + """ + + UNKNOWN = "unknown" + """ + The cause of the selection change is unknown or could not be determined. + """ + + TAP = "tap" + """ + The user tapped on the text and that caused the selection (or the location of the + cursor) to change. + """ + + DOUBLE_TAP = "doubleTap" + """ + The user tapped twice in quick succession on the text and that caused the + selection (or the location of the cursor) to change. + """ + + LONG_PRESS = "longPress" + """ + The user long-pressed the text and that caused the selection (or the location of + the cursor) to change. + """ + + FORCE_PRESS = "forcePress" + """ + The user force-pressed the text and that caused the selection (or the location of + the cursor) to change. + """ + + KEYBOARD = "keyboard" + """ + The user used the keyboard to change the selection or the location of the cursor. + + Keyboard-triggered selection changes may be caused by the IME as well as by + accessibility tools (e.g. TalkBack on Android). + """ + + TOOLBAR = "toolbar" + """ + The user used the selection toolbar to change the selection or the location of + the cursor. + + An example is when the user taps on select all in the tool bar. + """ + + DRAG = "drag" + """ + The user used the mouse to change the selection by dragging over a piece of text. + """ + + SCRIBBLE = "scribble" + """ + The user used iPadOS 14+ Scribble to change the selection. + """ + + +@dataclass +class TextSelectionChangeEvent(Event[EventControlType]): + text: str + cause: TextSelectionChangeCause + selection: TextSelection + + +@control("Text") +class Text(ConstrainedControl): + """ + Text is a control for displaying text. + + Online docs: https://flet.dev/docs/controls/text + """ + + value: Optional[str] = "" + """ + The text displayed. + """ + + spans: Optional[list[TextSpan]] = None + """ + The list of [`TextSpan`](https://flet.dev/docs/reference/types/textspan) + objects to build a rich text paragraph. + """ + + text_align: Optional[TextAlign] = None + """ + Text horizontal align. + + Value is of type [`TextAlign`](https://flet.dev/docs/reference/types/textalign) + and defaults to `TextAlign.LEFT`. + """ + + font_family: Optional[str] = None + """ + System or custom font family to render text with. See + [`Fonts`](https://flet.dev/docs/controls/page#fonts) cookbook guide for + instructions on how to import and use custom fonts in your application. + """ + + size: OptionalNumber = None + """ + Text size in virtual pixels. + + Value is of type `OptionalNumber` and defaults to `14`. + """ + + weight: Optional[FontWeight] = None + """ + Font weight. + + Value is of type [`FontWeight`](https://flet.dev/docs/reference/types/fontweight) + and defaults to `FontWeight.NORMAL`. + """ + + italic: Optional[bool] = None + """ + `True` to use italic typeface. + + Value is of type `bool` and defaults to `False`. + """ + + style: Optional[TextStyle] = None + """ + The text's style. + + Value is of type [`TextStyle`](https://flet.dev/docs/reference/types/textstyle). + """ + + theme_style: Optional[TextThemeStyle] = None + """ + Pre-defined text style. + + Value is of type [`TextThemeStyle`](https://flet.dev/docs/reference/types/textthemestyle). + """ + + max_lines: Optional[int] = None + """ + An optional maximum number of lines for the text to span, wrapping if necessary. + + If the text exceeds the given number of lines, it will be truncated according to + `overflow`. + + If this is 1, text will not wrap. Otherwise, text will be wrapped at the edge of + the box. + """ + + overflow: Optional[TextOverflow] = None + """ + Controls how text overflows. + + Value is of type [`TextOverflow`](https://flet.dev/docs/reference/types/textoverflow) + and defaults to `TextOverflow.FADE`. + """ + + selectable: Optional[bool] = None + """ + Whether the text should be selectable. + + Defaults to `False`. + """ + + no_wrap: Optional[bool] = None + """ + If `False` (default) the text should break at soft line breaks. + + If `True`, the glyphs in the text will be positioned as if there was unlimited + horizontal space. + + Value is of type `bool` and defaults to `False`. + """ + + color: OptionalColorValue = None + """ + Text foreground [color](https://flet.dev/docs/reference/colors). + """ + + bgcolor: OptionalColorValue = None + """ + Text background [color](https://flet.dev/docs/reference/colors). + """ + + semantics_label: Optional[str] = None + """ + An alternative semantics label for this text. + + If present, the semantics of this control will contain this value instead of the + actual text. This will overwrite any of the `TextSpan.semantics_label`s. + + This is useful for replacing abbreviations or shorthands with the full text value: + + Value is of type `str`. + + ```python + ft.Text("$$", semantics_label="Double dollars") + ``` + """ + + show_selection_cursor: Optional[bool] = None + """ + TBD + """ + + enable_interactive_selection: Optional[bool] = None + """ + TBD + """ + + selection_cursor_width: OptionalNumber = None + """ + TBD + """ + + selection_cursor_height: OptionalNumber = None + """ + TBD + """ + + selection_cursor_color: OptionalColorValue = None + """ + TBD + """ + + on_tap: OptionalControlEventHandler["Text"] = None + """ + TBD + """ + + on_selection_change: OptionalEventHandler[TextSelectionChangeEvent["Text"]] = None + """ + TBD + """ diff --git a/sdk/python/packages/flet/src/flet/controls/core/text_span.py b/sdk/python/packages/flet/src/flet/controls/core/text_span.py new file mode 100644 index 000000000..aaf1e405e --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/text_span.py @@ -0,0 +1,78 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.text_style import TextStyle +from flet.controls.types import UrlTarget + +__all__ = ["TextSpan"] + + +@control("TextSpan") +class TextSpan(Control): + """ + A span of [Text](https://flet.dev/docs/controls/text). + """ + + text: Optional[str] = None + """ + The text contained in this span. + + If both `text` and `spans` are defined, the `text` will precede the `spans`. + """ + + style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) to apply to + this span. + """ + + spans: Optional[list["TextSpan"]] = None + """ + Additional spans to include as children. + + If both `text` and `spans` are defined, the `text` will precede the `spans`. + """ + + url: Optional[str] = None + """ + The URL to open when the span is clicked. If registered, `on_click` event is fired + after that. + """ + + url_target: Optional[UrlTarget] = None + """ + Where to open URL in the web mode. + + Value is of [`UrlTarget`](https://flet.dev/docs/reference/types/urltarget) enum. + Defaults to `UrlTarget.BLANK`. + """ + + semantics_label: Optional[str] = None + """ + An alternative semantics label for this text. + + If present, the semantics of this control will contain this value instead of the + actual text. + """ + + spell_out: Optional[bool] = None + """ + TBD + """ + + on_click: OptionalControlEventHandler["TextSpan"] = None + """ + Fires when the span is clicked. + """ + + on_enter: OptionalControlEventHandler["TextSpan"] = None + """ + Triggered when a mouse pointer has entered the span. + """ + + on_exit: OptionalControlEventHandler["TextSpan"] = None + """ + Triggered when a mouse pointer has exited the span. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/core/transparent_pointer.py b/sdk/python/packages/flet/src/flet/controls/core/transparent_pointer.py new file mode 100644 index 000000000..b12642507 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/transparent_pointer.py @@ -0,0 +1,29 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control + +__all__ = ["TransparentPointer"] + + +@control("TransparentPointer") +class TransparentPointer(ConstrainedControl): + """ + TransparentPointer is the solution to ["How to pass through all gestures between + two widgets in Stack"](https://stackoverflow.com/questions/65269190/pass-trough-all- + gestures-between-two-widgets-in-stack) problem. + + For example, if there is an [`ElevatedButton`](https://flet.dev/docs/controls/ + elevatedbutton) inside [`Container`](https://flet.dev/docs/controls/container) with + [`GestureDetector`](https://flet.dev/docs/controls/gesturedetector) then tapping on + a button won't be "visible" to a gesture detector behind it. With + `TransparentPointer` a tapping event doesn't stop on a button, but goes up to the + parent, similar to event bubbling in HTML/JS. + """ + + content: Optional[Control] = None + """ + The `Control` that should be displayed inside the TransparentPointer. + """ + diff --git a/sdk/python/packages/flet/src/flet/controls/core/view.py b/sdk/python/packages/flet/src/flet/controls/core/view.py new file mode 100644 index 000000000..566dafac9 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/view.py @@ -0,0 +1,186 @@ +import asyncio +from dataclasses import field +from typing import Optional, Union + +from flet.controls.base_control import BaseControl, control +from flet.controls.box import BoxDecoration +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.cupertino.cupertino_app_bar import CupertinoAppBar +from flet.controls.cupertino.cupertino_navigation_bar import CupertinoNavigationBar +from flet.controls.material.app_bar import AppBar +from flet.controls.material.bottom_app_bar import BottomAppBar +from flet.controls.material.floating_action_button import FloatingActionButton +from flet.controls.material.navigation_bar import NavigationBar +from flet.controls.material.navigation_drawer import NavigationDrawer +from flet.controls.padding import OptionalPaddingValue +from flet.controls.scrollable_control import ScrollableControl +from flet.controls.transform import OffsetValue +from flet.controls.types import ( + CrossAxisAlignment, + FloatingActionButtonLocation, + MainAxisAlignment, + Number, + OptionalColorValue, +) + +__all__ = ["View"] + + +@control("View") +class View(ScrollableControl, ConstrainedControl): + """ + View is the top most container for all other controls. + + A root view is automatically created when a new user session started. From layout + perspective the View represents a `Column`(https://flet.dev/docs/controls/column/) + control, so it has a similar behavior and shares same properties. + + Online docs: https://flet.dev/docs/controls/view + """ + + route: Optional[str] = None + """ + View's route - not currently used by Flet framework, but can be used in a user + program to update [`page.route`](https://flet.dev/docs/controls/page#route) when a + view popped. + """ + + controls: list[BaseControl] = field(default_factory=list) + """ + A list of `Control`s to display on the Page. + + For example, to add a new control to a page: + + ```python + page.controls.append(ft.Text("Hello!")) + page.update() + ``` + + or to get the same result as above using `page.add()` shortcut method: + + ```python + page.add(ft.Text("Hello!")) + ``` + + To remove the top most control on the page: + + ```python + page.controls.pop() + page.update() + ``` + + Value is of a list of `Control`s. + """ + + appbar: Optional[Union[AppBar, CupertinoAppBar]] = None + """ + A [`AppBar`](https://flet.dev/docs/controls/appbar) control to display at the top of + the Page. + """ + + bottom_appbar: Optional[BottomAppBar] = None + """ + TBD + """ + + floating_action_button: Optional[FloatingActionButton] = None + """ + A [`FloatingActionButton`](https://flet.dev/docs/controls/floatingactionbutton) + control to display on top of Page content. + """ + + floating_action_button_location: Optional[ + Union[FloatingActionButtonLocation, OffsetValue] + ] = None + """ + Describes position of [`floating_action_button`](#floating_action_button) + + Value is of type [`FloatingActionButtonLocation`](https://flet.dev/docs/controls/ + floatingactionbutton) + """ + + navigation_bar: Union[NavigationBar, CupertinoNavigationBar, None] = None + """ + TBD + """ + + drawer: Optional[NavigationDrawer] = None + """ + A [`NavigationDrawer`](https://flet.dev/docs/controls/navigationdrawer) control to + display as a panel sliding from the start edge of the view. + """ + + end_drawer: Optional[NavigationDrawer] = None + """ + A [`NavigationDrawer`](https://flet.dev/docs/controls/navigationdrawer) control to + display as a panel sliding from the end edge of the view. + """ + + vertical_alignment: Optional[MainAxisAlignment] = None + """ + How the child Controls should be placed vertically. + + Value is of type [`MainAxisAlignment`](https://flet.dev/docs/reference/types/ + mainaxisalignment) and defaults to `MainAxisAlignment.START`. + """ + + horizontal_alignment: Optional[CrossAxisAlignment] = None + """ + How the child Controls should be placed horizontally. + + Value is of type [`CrossAxisAlignment`](https://flet.dev/docs/reference/types/ + crossaxisalignment) and defaults to `CrossAxisAlignment.START`. + """ + + spacing: Number = 10 + """ + Vertical spacing between controls on the Page. Default value is 10 virtual pixels. + Spacing is applied only when `vertical_alignment` is set to + `MainAxisAlignment.START`, `MainAxisAlignment.END` or `MainAxisAlignment.CENTER`. + + Value is of type `Number` and defaults to `10` + """ + + padding: OptionalPaddingValue = None + """ + A space between page contents and its edges. + + Value is of type [`PaddingValue`](https://flet.dev/docs/reference/types/aliases# + paddingvalue) and defaults to `padding.all(10)`. + """ + + bgcolor: OptionalColorValue = None + """ + Background [color](https://flet.dev/docs/reference/colors) of the view. + """ + + decoration: Optional[BoxDecoration] = None + """ + The background decoration. + + Value is of type [`BoxDecoration`](https://flet.dev/docs/reference/types/ + boxdecoration). + """ + + foreground_decoration: Optional[BoxDecoration] = None + """ + The foreground decoration. + + Value is of type [`BoxDecoration`](https://flet.dev/docs/reference/types/ + boxdecoration). + """ + + can_pop: bool = True + on_confirm_pop: OptionalControlEventHandler["View"] = None + + def confirm_pop(self, should_pop: bool) -> None: + asyncio.create_task(self.confirm_pop_async(should_pop)) + + async def confirm_pop_async(self, should_pop: bool) -> None: + await self._invoke_method_async("confirm_pop", {"should_pop": should_pop}) + + # Magic methods + def __contains__(self, item: Control) -> bool: + return item in self.controls diff --git a/sdk/python/packages/flet/src/flet/controls/core/window.py b/sdk/python/packages/flet/src/flet/controls/core/window.py new file mode 100644 index 000000000..3ee7cdd88 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/window.py @@ -0,0 +1,302 @@ +import asyncio +from dataclasses import dataclass +from enum import Enum +from typing import Optional + +from flet.controls.alignment import Alignment +from flet.controls.base_control import BaseControl, control +from flet.controls.control_event import Event, EventControlType, OptionalEventHandler +from flet.controls.types import ( + Brightness, + Number, + OptionalColorValue, + OptionalNumber, +) + +__all__ = ["Window", "WindowEvent", "WindowEventType", "WindowResizeEdge"] + + +class WindowEventType(Enum): + CLOSE = "close" + FOCUS = "focus" + BLUR = "blur" + HIDE = "hide" + SHOW = "show" + MAXIMIZE = "maximize" + UNMAXIMIZE = "unmaximize" + MINIMIZE = "minimize" + RESTORE = "restore" + RESIZE = "resize" + RESIZED = "resized" + MOVE = "move" + MOVED = "moved" + LEAVE_FULL_SCREEN = "leave-full-screen" + ENTER_FULL_SCREEN = "enter-full-screen" + + +class WindowResizeEdge(Enum): + TOP = "top" + LEFT = "left" + RIGHT = "right" + BOTTOM = "bottom" + TOP_LEFT = "topLeft" + BOTTOM_LEFT = "bottomLeft" + TOP_RIGHT = "topRight" + BOTTOM_RIGHT = "bottomRight" + + +@dataclass +class WindowEvent(Event[EventControlType]): + type: WindowEventType + + +# todo: raise FletExceptions when a method cant be called on the running platform + + +@control("Window") +class Window(BaseControl): + """ + All properties and methods of the `Window` class are available only on Desktop 🖥️ + platforms. + """ + + bgcolor: OptionalColorValue = None + """ + Sets background https://flet.dev/docs/reference/colors of an application window. + + Use together with `page.bgcolor` to make a window transparent. + """ + + width: OptionalNumber = None + """ + Defines the width of the app window. + """ + + height: OptionalNumber = None + """ + Defines the height of the app window. + """ + + top: OptionalNumber = None + """ + Defines the vertical position of a native OS window - a distance in virtual + pixels from the top edge of the screen. + """ + + left: OptionalNumber = None + """ + Defines the horizontal position of the app window - a distance in virtual + pixels from the left edge of the screen. + """ + + max_width: OptionalNumber = None + """ + Defines the maximum width of the app window. + """ + + max_height: OptionalNumber = None + """ + Defines the maximum height of the app window. + """ + + min_width: OptionalNumber = None + """ + Defines the minimum width of the app window. + """ + + min_height: OptionalNumber = None + """ + Defines the minimum height of the app window. + """ + + opacity: Number = 1.0 + """ + Defines the opacity of a native OS window. + + Value must be between `0.0` and `1.0`. + """ + + aspect_ratio: OptionalNumber = None + """ + TBD + """ + + brightness: Optional[Brightness] = None + """ + TBD + """ + + maximized: bool = False + """ + Whether the app window is maximized. Set to `True` to maximize programmatically. + """ + + minimized: bool = False + """ + Whether the app window is minimized. Set to `True` to minimize programmatically. + """ + + minimizable: bool = True + """ + Whether the app window can be minimized through the window's "Minimize" button. + """ + + maximizable: bool = True + """ + Whether to hide/disable native OS window's "Maximize" button. + """ + + resizable: bool = True + """ + Whether the app window can be resized. + """ + + movable: bool = True + """ + Whether the app window can be moved. + + Has effect on macOS only. + """ + + full_screen: bool = False + """ + Whether to switch app's native OS window to a fullscreen mode. + """ + + always_on_top: bool = False + """ + Whether the app window should always be displayed on top of other windows. + """ + + always_on_bottom: bool = False + """ + Whether the app window should always be displayed below other windows. + + Has effect on Linux and Windows only. + """ + + prevent_close: bool = False + """ + Set to `True` to intercept the native close signal. Could be used to implement + app exit confirmation logic. + """ + + skip_task_bar: bool = False + """ + Set to `True` to hide application from the Task Bar (Windows) or Dock (macOS). + """ + + title_bar_hidden: bool = False + """ + Whether to hide the app window's title bar. + """ + + title_bar_buttons_hidden: bool = False + """ + Whether to hide the app window's title bar buttons. + + Has effect on macOS only. + """ + + frameless: bool = False + """ + Whether the app window should be frameless. + """ + + progress_bar: OptionalNumber = None + """ + The value from `0.0` to `1.0` to display a progress bar on Task Bar or Dock. + """ + + focused: bool = True + """ + Set to `True` to focus a native OS window. + """ + + visible: bool = True + """ + Whether to make the app window visible. Used when the app starts hidden. + """ + + shadow: bool = True + """ + Whether to display a shadow around the app window. + """ + + alignment: Optional[Alignment] = None + """ + Defines the alignment of the app window. + + Value is of type https://flet.dev/docs/reference/types/alignment. + """ + + badge_label: Optional[str] = None + """ + Sets a badge label on the app window. + + Has effect on macOS only. + """ + + icon: Optional[str] = None + """ + The icon of the app window. + + Has effect on Windows only. + """ + + ignore_mouse_events: bool = False + """ + Whether the app window should ignore mouse events, passing them to the window + below it. If this window has focus, it will still receive keyboard events. + """ + + on_event: OptionalEventHandler[WindowEvent] = None + """ + Fires when app window changes its state: position, size, maximized, minimized, etc. + """ + + def __post_init__(self, ref) -> None: + super().__post_init__(ref) + self._i = 2 + + async def wait_until_ready_to_show_async(self): + await self._invoke_method_async("wait_until_ready_to_show") + + def wait_until_ready_to_show(self): + asyncio.create_task(self.wait_until_ready_to_show_async()) + + async def destroy_async(self): + await self._invoke_method_async("destroy") + + def destroy(self): + asyncio.create_task(self.destroy_async()) + + async def center_async(self): + await self._invoke_method_async("center") + + def center(self): + asyncio.create_task(self.center_async()) + + async def close_async(self): + await self._invoke_method_async("close") + + def close(self): + asyncio.create_task(self.close_async()) + + async def to_front_async(self): + await self._invoke_method_async("to_front") + + def to_front(self): + asyncio.create_task(self.to_front_async()) + + async def start_dragging_async(self): + await self._invoke_method_async("start_dragging") + + def start_dragging(self): + asyncio.create_task(self.start_dragging_async()) + + async def start_resizing_async(self, edge: WindowResizeEdge): + await self._invoke_method_async("start_resizing", {"edge": edge}) + + def start_resizing(self, edge: WindowResizeEdge): + asyncio.create_task(self.start_resizing_async(edge)) diff --git a/sdk/python/packages/flet/src/flet/controls/core/window_drag_area.py b/sdk/python/packages/flet/src/flet/controls/core/window_drag_area.py new file mode 100644 index 000000000..6fe28549c --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/core/window_drag_area.py @@ -0,0 +1,61 @@ + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalEventHandler +from flet.controls.core.window import WindowEvent +from flet.controls.events import DragEndEvent, DragStartEvent + + +@control("WindowDragArea") +class WindowDragArea(ConstrainedControl): + """ + A control for drag to move, maximize and restore application window. + + When you have hidden the title bar with `page.window_title_bar_hidden`, you can add + this control to move the window position. + + Online docs: https://flet.dev/docs/controls/windowdragarea + """ + + content: Control + """ + The content of this `WindowDragArea`. + """ + + maximizable: bool = True + """ + Whether double-clicking on the `WindowDragArea` should maximize/maximize the app's + window. + Defaults to `True`. + """ + + on_double_tap: OptionalEventHandler[WindowEvent["WindowDragArea"]] = None + """ + Fires when the `WindowDragArea` is double-tapped and `maximizable=True`. + + Event handler argument is of type `WindowEvent`, + with its `type` property being one of the following: `WindowEventType.MAXIMIZE`, + `WindowEventType.UNMAXIMIZE` + """ + + on_drag_start: OptionalEventHandler[DragStartEvent["WindowDragArea"]] = None + """ + Fires when a pointer has contacted the screen and has begun to move/drag. + + Event handler argument is of type + [`DragStartEvent`](https://flet.dev/docs/reference/types/dragstartevent). + """ + + on_drag_end: OptionalEventHandler[DragEndEvent["WindowDragArea"]] = None + """ + Fires when a pointer that was previously in contact with the screen and + moving/dragging is no longer in contact with the screen. + + Event handler argument is of type + [`DragEndEvent`](https://flet.dev/docs/reference/types/dragendevent). + """ + + def before_update(self): + super().before_update() + assert self.content.visible, "content must be visible" diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/__init__.py b/sdk/python/packages/flet/src/flet/controls/cupertino/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_action_sheet.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_action_sheet.py new file mode 100644 index 000000000..24c555f8e --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_action_sheet.py @@ -0,0 +1,49 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.types import StrOrControl + +__all__ = ["CupertinoActionSheet"] + + +@control("CupertinoActionSheet") +class CupertinoActionSheet(ConstrainedControl): + """ + An iOS-style action sheet. + + Online docs: https://flet.dev/docs/controls/cupertinoactionsheet + """ + + title: Optional[StrOrControl] = None + """ + A control containing the title of the action sheet. + + Typically a [`Text`](https://flet.dev/docs/controls/text) control. + """ + + message: Optional[StrOrControl] = None + """ + A control containing a descriptive message that provides more details about the + reason for the alert. + + Typically a [`Text`](https://flet.dev/docs/controls/text) control. + """ + + actions: Optional[list[Control]] = None + """ + A list of action buttons to be shown in the sheet. + + These actions are typically [`CupertinoActionSheetAction`](https://flet.dev/docs/controls/cupertinoactionsheetaction)s. + + This list must have at least one action. + """ + + cancel: Optional[Control] = None + """ + An optional control to be shown below the actions but grouped separately from them. + + Typically a [`CupertinoActionSheetAction`](https://flet.dev/docs/controls/cupertinoactionsheetaction) + button. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_action_sheet_action.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_action_sheet_action.py new file mode 100644 index 000000000..8b099554c --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_action_sheet_action.py @@ -0,0 +1,54 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.types import MouseCursor, StrOrControl + +__all__ = ["CupertinoActionSheetAction"] + + +@control("CupertinoActionSheetAction") +class CupertinoActionSheetAction(ConstrainedControl): + """ + An action button typically used in a CupertinoActionSheet. + + Online docs: https://flet.dev/docs/controls/cupertinoactionsheetaction + """ + + content: StrOrControl + """ + The child control to be shown in this action button. + + In case both `text` and `content` are provided, then `content` will be used. + """ + + default: bool = False + """ + Whether this action should receive the style of an emphasized, default action. + + Defaults to `False`. + """ + + destructive: bool = False + """ + Whether this action should receive the style of a destructive action. + + Defaults to `False`. + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + TBD + """ + + on_click: OptionalControlEventHandler["CupertinoActionSheetAction"] = None + """ + Fires when this action button is clicked. + """ + + def before_update(self): + super().before_update() + assert isinstance(self.content, str) or self.content.visible, ( + "content must be a string or a visible Control" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_activity_indicator.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_activity_indicator.py new file mode 100644 index 000000000..993080d3e --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_activity_indicator.py @@ -0,0 +1,30 @@ +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.types import Number, OptionalColorValue + +__all__ = ["CupertinoActivityIndicator"] + + +@control("CupertinoActivityIndicator") +class CupertinoActivityIndicator(ConstrainedControl): + """ + An iOS-style activity indicator that spins clockwise. + + Online docs: https://flet.dev/docs/controls/cupertinoactivityindicator + """ + + radius: Number = 10 + """ + The radius of the activity indicator. + """ + + color: OptionalColorValue = None + """ + Defines the [color](https://flet.dev/docs/reference/colors) of the activity + indicator. + """ + + animating: bool = True + """ + Whether the activity indicator is running its animation. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_alert_dialog.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_alert_dialog.py new file mode 100644 index 000000000..6fc44ca58 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_alert_dialog.py @@ -0,0 +1,78 @@ +from dataclasses import field +from typing import Optional + +from flet.controls.animation import Animation, AnimationCurve +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.dialog_control import DialogControl +from flet.controls.duration import Duration +from flet.controls.types import StrOrControl + +__all__ = ["CupertinoAlertDialog"] + + +@control("CupertinoAlertDialog") +class CupertinoAlertDialog(DialogControl): + """ + An iOS-style alert dialog. + + An alert dialog informs the user about situations that require acknowledgement. An + alert dialog has an optional title and an optional list of actions. The title is + displayed above the content and the actions are displayed below the content. + + Online docs: https://flet.dev/docs/controls/cupertinoalertdialog + """ + + modal: bool = False + """ + If set to True, dialog cannot be dismissed by clicking the area outside of it. + The default value is False. + """ + + title: Optional[StrOrControl] = None + """ + The (optional) title of the dialog is displayed in a large font at the top of the + dialog. + + Typically a [`Text`](https://flet.dev/docs/controls/text) control. + """ + + content: Optional[Control] = None + """ + The (optional) content of the dialog is displayed in the center of the dialog in a + lighter font. + + Typically this is a [`Column`](https://flet.dev/docs/controls/column) that contains + the dialog's [`Text`](https://flet.dev/docs/controls/text) message. + """ + + actions: list[Control] = field(default_factory=list) + """ + The (optional) set of actions that are displayed at the bottom of the dialog. + + Typically this is a list of + [`CupertinoDialogAction`](https://flet.dev/docs/controls/cupertinodialogaction) + controls. + """ + + inset_animation: Animation = field( + default_factory=lambda: Animation( + curve=AnimationCurve.DECELERATE, duration=Duration(milliseconds=100) + ) + ) + """ + The animation style to be used when the system keyboard intrudes into the space + that the dialog is placed in. + + Value is of type + [`AnimationStyle`](https://flet.dev/docs/reference/types/animationstyle). + """ + + def before_update(self): + super().before_update() + assert ( + (isinstance(self.title, str) or self.title.visible) + or (self.content and self.content.visible) + or any(a.visible for a in self.actions) + ), "AlertDialog has nothing to display. Provide at minimum one of the " + "following: title, content, actions" diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_app_bar.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_app_bar.py new file mode 100644 index 000000000..5c779311b --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_app_bar.py @@ -0,0 +1,149 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.border import Border +from flet.controls.control import Control +from flet.controls.padding import OptionalPaddingValue +from flet.controls.types import Brightness, OptionalColorValue, StrOrControl + +__all__ = ["CupertinoAppBar"] + + +@control("CupertinoAppBar") +class CupertinoAppBar(Control): + """ + An iOS-styled application bar. + + Online docs: https://flet.dev/docs/controls/cupertinoappbar + """ + + leading: Optional[Control] = None + """ + A `Control` to display at the start of this app bar. Typically the leading control + is an [`Icon`](https://flet.dev/docs/controls/icon) or an + [`IconButton`](https://flet.dev/docs/controls/iconbutton). + + If `None` and `automatically_imply_leading = True`, an appropriate button will be + automatically created. + """ + + middle: Optional[StrOrControl] = None + """ + A `Control` to display in the middle of this app bar. Typically a + [`Text`](https://flet.dev/docs/controls/text) or a segmented control. + """ + + title: Optional[StrOrControl] = None + """ + TBD + """ + + trailing: Optional[Control] = None + """ + A Control to place at the end of the app bar. Normally additional actions taken on + the page such as a search or edit function. + """ + + bgcolor: OptionalColorValue = None + """ + The fill [color](https://flet.dev/docs/reference/colors) to use for an AppBar. + Default color is defined by current theme. + """ + + automatically_imply_leading: Optional[bool] = None + """ + Controls whether we should try to imply the leading control if None. + + If `True` and `leading` is null, automatically try to deduce what the leading + widget should be. If `False` and `leading` is None, leading space is given to + title. If leading widget is not None, this parameter has no effect. + """ + + automatically_imply_middle: Optional[bool] = None + """ + Controls whether we should try to imply the middle control if None. + + If `True` and `middle` is null, automatically fill in a Text control with the + current route's title. If middle control is not None, this parameter has no effect. + """ + + automatically_imply_title: Optional[bool] = None + """ + TBD + """ + + border: Optional[Border] = None + """ + The border of the app bar. By default, a single pixel bottom border side is + rendered. + + Value is of type + [`Border`](https://flet.dev/docs/reference/types/border). + """ + + padding: OptionalPaddingValue = None + """ + Defines the padding for the contents of the app bar. + + Padding is an instance of + [`Padding`](https://flet.dev/docs/reference/types/padding) class. + + If `None`, the app bar will adopt the following defaults: + + - vertically, contents will be sized to the same height as the app bar itself minus + the status bar. + - horizontally, padding will be `16` pixels according to iOS specifications unless + the leading widget is an automatically inserted back button, in which case the + padding will be `0`. + + Vertical padding won't change the height of the app bar. + """ + + transition_between_routes: Optional[bool] = None + """ + TBD + """ + + previous_page_title: Optional[str] = None + """ + TBD + """ + + brightness: Optional[Brightness] = None + """ + The brightness of the specified `bgcolor`. + + Setting this value changes the style of the system status bar. It is typically used + to increase the contrast ratio of the system status bar over `bgcolor`. + + If `None` (the default), its value will be inferred from the relative luminance of + the `bgcolor`. + + Value is of type + [`Brightness`](https://flet.dev/docs/reference/types/brightness). + """ + + automatic_background_visibility: Optional[bool] = None + """ + Whether the navigation bar should appear transparent when content is scrolled under + it. + + If `False`, the navigation bar will display its `bgcolor`. + + Defaults to `True`. + """ + + enable_background_filter_blur: Optional[bool] = None + """ + Whether to have a blur effect when a non-opaque `bgcolor` is used. + + This will only be respected when `automatic_background_visibility` is `False` or + until content scrolls under the navbar. + + Defaults to `True`. + """ + + large: Optional[bool] = None + """ + TBD + """ diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_bottom_sheet.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_bottom_sheet.py new file mode 100644 index 000000000..0877b8f34 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_bottom_sheet.py @@ -0,0 +1,43 @@ +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.dialog_control import DialogControl +from flet.controls.padding import OptionalPaddingValue +from flet.controls.types import OptionalColorValue, OptionalNumber + +__all__ = ["CupertinoBottomSheet"] + + +@control("CupertinoBottomSheet") +class CupertinoBottomSheet(DialogControl): + """ + A Cupertino version of modal bottom sheet. + + Online docs: https://flet.dev/docs/controls/cupertinobottomsheet + """ + + content: Control + """ + The content of the bottom sheet. + """ + + modal: bool = False + """ + Whether this bottom sheet can be dismissed/closed by clicking the area outside of + it. + """ + + bgcolor: OptionalColorValue = None + """ + The sheet's background [color](https://flet.dev/docs/reference/colors). + """ + + height: OptionalNumber = None + """ + The height of the bottom sheet. + """ + + padding: OptionalPaddingValue = None + """ + The sheet's padding. The value is an instance of + [`Padding`](https://flet.dev/docs/reference/types/padding) class or a number. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_button.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_button.py new file mode 100644 index 000000000..9048ec854 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_button.py @@ -0,0 +1,157 @@ +import asyncio +from enum import Enum +from typing import Optional + +from flet.controls.alignment import OptionalAlignment +from flet.controls.base_control import control +from flet.controls.border_radius import BorderRadiusValue +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.padding import OptionalPaddingValue +from flet.controls.types import ( + IconValueOrControl, + Number, + OptionalColorValue, + StrOrControl, + UrlTarget, +) + +__all__ = ["CupertinoButton", "CupertinoButtonSize"] + + +class CupertinoButtonSize(Enum): + SMALL = "small" + MEDIUM = "medium" + LARGE = "large" + + +@control("CupertinoButton") +class CupertinoButton(ConstrainedControl): + """ + An iOS-style button. + + Online docs: https://flet.dev/docs/controls/cupertinobutton + """ + + content: Optional[StrOrControl] = None + """ + A Control representing custom button content. + """ + + icon: Optional[IconValueOrControl] = None + """ + Icon shown in the button. + """ + + icon_color: OptionalColorValue = None + """ + Icon [color](https://flet.dev/docs/reference/colors). + """ + + bgcolor: OptionalColorValue = None + """ + Button's background [color](https://flet.dev/docs/reference/colors). + """ + + color: OptionalColorValue = None + """ + Button's text [color](https://flet.dev/docs/reference/colors). + """ + + disabled_bgcolor: OptionalColorValue = None + """ + The background [color](https://flet.dev/docs/reference/colors) of the button when + it is disabled. + """ + + opacity_on_click: Number = 0.4 + """ + Defines the opacity of the button when it is clicked. When not pressed, + the button has an opacity of `1.0`. + + Defaults to `0.4`. + """ + + min_size: Number = None + """ + The minimum size of the button. + + Defaults to `44.0`. + """ + + size_style: CupertinoButtonSize = CupertinoButtonSize.LARGE + """ + TBD + """ + + padding: OptionalPaddingValue = None + """ + The amount of space to surround the `content` control inside the bounds of the + button. + """ + + alignment: OptionalAlignment = None + """ + TBD + """ + + border_radius: BorderRadiusValue = 8.0 + """ + TBD + """ + + url: Optional[str] = None + """ + The URL to open when the button is clicked. If registered, `on_click` event is + fired after that. + """ + + url_target: Optional[UrlTarget] = None + """ + Where to open URL in the web mode. + + Value is of type [`UrlTarget`](https://flet.dev/docs/reference/types/urltarget) and + defaults to `UrlTarget.BLANK`. + """ + + autofocus: bool = False + """ + TBD + """ + + focus_color: OptionalColorValue = None + """ + TBD + """ + + on_click: OptionalControlEventHandler["CupertinoButton"] = None + """ + Fires when a user clicks the button. + """ + + on_long_press: OptionalControlEventHandler["CupertinoButton"] = None + """ + Fires when a user long-presses the button. + """ + + on_focus: OptionalControlEventHandler["CupertinoButton"] = None + """ + Fires when the button receives focus. + """ + + on_blur: OptionalControlEventHandler["CupertinoButton"] = None + """ + Fires when the button loses focus. + """ + + def before_update(self): + super().before_update() + assert 0 <= self.opacity_on_click <= 1, ( + "opacity_on_click must be between 0 and 1 inclusive" + ) + + async def focus_async(self): + await self._invoke_method_async("focus") + + def focus(self): + asyncio.create_task(self.focus_async()) diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_checkbox.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_checkbox.py new file mode 100644 index 000000000..5b77879e9 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_checkbox.py @@ -0,0 +1,142 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.border import BorderSide +from flet.controls.buttons import OutlinedBorder +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.control_state import ControlStateValue +from flet.controls.types import ( + ColorValue, + LabelPosition, + MouseCursor, + OptionalColorValue, +) + +__all__ = ["CupertinoCheckbox"] + + +@control("CupertinoCheckbox") +class CupertinoCheckbox(ConstrainedControl): + """ + A macOS style checkbox. Checkbox allows to select one or more items from a group, + or switch between two mutually exclusive options (checked or unchecked, on or off). + + Online docs: https://flet.dev/docs/controls/cupertinocheckbox + """ + + label: Optional[str] = None + """ + The clickable label to display on the right of a checkbox. + """ + + label_position: LabelPosition = LabelPosition.RIGHT + """ + Defines on which side of the checkbox the `label` should be shown. + + Value is of type [`LabelPosition`](https://flet.dev/docs/reference/types/labelposition) + and defaults to `RIGHT`. + """ + + value: Optional[bool] = None + """ + Current value of the checkbox. + """ + + tristate: bool = True + """ + If `True` the checkbox's value can be `True`, `False`, or `None`. + + Checkbox displays a dash when its value is null. + """ + + autofocus: bool = False + """ + True if the control will be selected as the initial focus. If there is more than + one control on a page with autofocus set, then the first one added to the page will + get focus. + """ + + check_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use for the check icon when + this checkbox is checked. + """ + + active_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used to fill checkbox when it + is checked/selected. + + If `fill_color` returns a non-null color in the `SELECTED` state, it will be used + instead of this color. + + Defaults to `Colors.PRIMARY`. + """ + + focus_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used for the checkbox's border + shadow when it has the input focus. + """ + + fill_color: Optional[ControlStateValue[ColorValue]] = None + """ + The [color](https://flet.dev/docs/reference/colors) used to fill the checkbox in + all or specific [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + states. + + The following states are supported: `DEFAULT`, `SELECTED`, `HOVERED`, `FOCUSED`, + and `DISABLED`. + + `active_color` is used as fallback color when the checkbox is in the `SELECTED` + state, `CupertinoColors.WHITE` at 50% opacity is used as fallback color when the + checkbox is in the `DISABLED` state, and `CupertinoColors.WHITE` otherwise. + """ + + shape: Optional[OutlinedBorder] = None + """ + The shape of the checkbox. + + Value is of type [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder) + and defaults to `RoundedRectangleBorder(radius=4)`. + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + The cursor for a mouse pointer entering or hovering over this control. + + Value is of type [`MouseCursor`](https://flet.dev/docs/reference/types/mousecursor). + """ + + semantics_label: Optional[str] = None + """ + The semantic label for the checkbox that will be announced by screen readers. + + This is announced by assistive technologies (e.g TalkBack/VoiceOver) and not shown + on the UI. + """ + + border_side: Optional[ControlStateValue[BorderSide]] = None + """ + Defines the checkbox's border sides in all or specific + [`ControlState`](https://flet.dev/docs/reference/types/controlstate) states. + + The following states are supported: `DEFAULT`, `PRESSED`, `SELECTED`, `HOVERED`, + `FOCUSED`, `DISABLED` and `ERROR`. + """ + + on_change: OptionalControlEventHandler["CupertinoCheckbox"] = None + """ + Fires when the state of the Checkbox is changed. + """ + + on_focus: OptionalControlEventHandler["CupertinoCheckbox"] = None + """ + Fires when the control has received focus. + """ + + on_blur: OptionalControlEventHandler["CupertinoCheckbox"] = None + """ + Fires when the control has lost focus. + """ diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_colors.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_colors.py similarity index 92% rename from sdk/python/packages/flet/src/flet/core/cupertino_colors.py rename to sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_colors.py index e44ffbd35..c92e6cca6 100644 --- a/sdk/python/packages/flet/src/flet/core/cupertino_colors.py +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_colors.py @@ -16,10 +16,12 @@ import random from enum import Enum -from typing import TYPE_CHECKING, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Optional, Union if TYPE_CHECKING: - from flet.core.types import ColorValue + from flet.controls.types import ColorValue + +__all__ = ["CupertinoColors"] class CupertinoColors(str, Enum): @@ -44,15 +46,16 @@ def with_opacity(opacity: Union[int, float], color: "ColorValue") -> str: @staticmethod def random( - exclude: Optional[List["CupertinoColors"]] = None, - weights: Optional[Dict["CupertinoColors", int]] = None, + exclude: Optional[list["CupertinoColors"]] = None, + weights: Optional[dict["CupertinoColors", int]] = None, ) -> Optional["CupertinoColors"]: """ Selects a random color, with optional exclusions and weights. Args: exclude: A list of colors members to exclude from the selection. - weights: A dictionary mapping color members to their respective weights for weighted random selection. + weights: A dictionary mapping color members to their respective weights for + weighted random selection. Returns: A randomly selected color, or None if all members are excluded. diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_context_menu.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_context_menu.py new file mode 100644 index 000000000..61e31d051 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_context_menu.py @@ -0,0 +1,44 @@ +from dataclasses import field + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.control import Control + +__all__ = ["CupertinoContextMenu"] + + +@control("CupertinoContextMenu") +class CupertinoContextMenu(AdaptiveControl): + """ + A full-screen modal route that opens up when the content is long-pressed. + + Online docs: https://flet.dev/docs/controls/cupertinocontextmenu + """ + + content: Control + """ + The child control to be shown. This is a required property. + + When the `CupertinoContextMenu` is long-pressed, the menu will open and this widget + will be moved to the new route and be expanded. This allows the child to resize to + fit in its place in the new route, if it doesn't size itself. + """ + + actions: list[Control] = field(default_factory=list) + """ + A list of action buttons to be shown in the menu. These actions are typically + [`CupertinoContextMenuAction`](https://flet.dev/docs/controls/cupertinocontextmenuaction)s. + This list must have at least one action. + """ + + enable_haptic_feedback: bool = True + """ + Whether a click on the `actions` should produce haptic feedback. + """ + + def before_update(self): + super().before_update() + assert self.content.visible, "content must be visible" + assert any( + a.visible for a in self.actions + ), "at least one action must be visible" diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_context_menu_action.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_context_menu_action.py new file mode 100644 index 000000000..b64f151d1 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_context_menu_action.py @@ -0,0 +1,48 @@ +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.types import IconValue, StrOrControl + +__all__ = ["CupertinoContextMenuAction"] + + +@control("CupertinoContextMenuAction") +class CupertinoContextMenuAction(AdaptiveControl): + """ + An action that can be added to a CupertinoContextMenu. + + Online docs: https://flet.dev/docs/controls/cupertinocontextmenuaction + """ + + content: StrOrControl + """ + String or Control to be shown in this action button. + """ + + default: bool = False + """ + Whether this action should receive the style of an emphasized, default action. + """ + + destructive: bool = False + """ + Whether this action should receive the style of a destructive action. + """ + + trailing_icon: Optional[IconValue] = None + """ + An optional icon to display at the right of the `text` or `content` control. + """ + + on_click: OptionalControlEventHandler["CupertinoContextMenuAction"] = None + """ + Fires when this action button is clicked. + """ + + def before_update(self): + super().before_update() + assert isinstance(self.content, str) or self.content.visible, ( + "content must be a string or a visible Control" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_date_picker.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_date_picker.py new file mode 100644 index 000000000..744772109 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_date_picker.py @@ -0,0 +1,217 @@ +from dataclasses import field +from datetime import date, datetime +from enum import Enum +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.duration import DateTimeValue +from flet.controls.types import Number, OptionalColorValue + +__all__ = [ + "CupertinoDatePicker", + "CupertinoDatePickerMode", + "CupertinoDatePickerDateOrder", +] + + +class CupertinoDatePickerMode(Enum): + TIME = "time" + DATE = "date" + DATE_AND_TIME = "dateAndTime" + MONTH_YEAR = "monthYear" + + +class CupertinoDatePickerDateOrder(Enum): + DAY_MONTH_YEAR = "dmy" + MONTH_YEAR_DAY = "myd" + YEAR_MONTH_DAY = "ymd" + YEAR_DAY_MONTH = "ydm" + + +@control("CupertinoDatePicker") +class CupertinoDatePicker(ConstrainedControl): + """ + An iOS-styled date picker. + + Online docs: https://flet.dev/docs/controls/cupertinodatepicker + """ + + value: DateTimeValue = field(default_factory=lambda: datetime.now()) + """ + The initial date and/or time of the picker. It must conform to the intervals + set in `first_date`, `last_date`, `min_year`, and `max_year` else an error + will be `ValueError` will be raised. + + Defaults to the present date and time. + """ + + first_date: Optional[DateTimeValue] = None + """ + The earliest allowable date that the user can select. + + Defaults to `None` - no limit. + + When not `None`, one can still scroll the picker to dates earlier than + `first_date`, with the exception that the `on_change` will not be called. + Once let go, the picker will scroll back to `first_date`. + + In `CupertinoDatePickerMode.TIME` mode, a time becomes unselectable if the + datetime produced by combining that particular time and the date part of + initialDateTime is earlier than `last_date`. So typically `first_date` needs + to be set to a datetime that is on the same date as initialDateTime. + """ + + last_date: Optional[DateTimeValue] = None + """ + The latest allowable date that the user can select. + + When not `None`, one can still scroll the picker to dates later than + `last_date`, with the exception that the `on_change` will not be called. + Once let go, the picker will scroll back to `last_date`. + + In `CupertinoDatePickerMode.TIME` mode, a time becomes unselectable if the + datetime produced by combining that particular time and the date part of + initialDateTime is later than `last_date`. So typically `last_date` needs to + be set to a datetime that is on the same date as initialDateTime. + + Defaults to `None` - no limit. + """ + + bgcolor: OptionalColorValue = None + """ + The background [color](/docs/reference/colors) of the date picker. + """ + + minute_interval: int = 1 + """ + The granularity of the minutes spinner, if it is shown in the current + `date_picker_mode`. Must be an integer factor of 60. + + Defaults to `1`. + """ + + minimum_year: int = 1 + """ + Minimum year to which the picker can be scrolled when in + `CupertinoDatePickerMode.DATE` mode. + + Defaults to `1`. + """ + + maximum_year: Optional[int] = None + """ + Maximum year to which the picker can be scrolled when in + `CupertinoDatePickerMode.DATE` mode. + + Defaults to `None` - no limit. + """ + + item_extent: Number = 32.0 + """ + The uniform height of all children. + + Defaults to `32`. + """ + + use_24h_format: bool = False + """ + If `True`, 24-hour time format is used else 12-hour time format is used. + + Defaults to `False`. + """ + + show_day_of_week: bool = False + """ + Whether to show day of week alongside day. + + Defaults to `False`. + """ + + date_picker_mode: CupertinoDatePickerMode = CupertinoDatePickerMode.DATE_AND_TIME + """ + The mode of the date picker. + + Value is of type [`CupertinoDatePickerMode`](https://flet.dev/docs/reference/types/cupertinodatepickermode) + and defaults to `CupertinoDatePickerMode.DATE_AND_TIME`. + """ + + date_order: Optional[CupertinoDatePickerDateOrder] = None + """ + The order in which the columns inside this picker are displayed. + + Value is of type [`CupertinoDatePickerDateOrder`](https://flet.dev/docs/reference/types/cupertinodatepickerdateorder). + + Note: + The final order in which the columns are displayed is also influenced by + the `date_picker_mode`. For example, when using + `date_picker_mode=CupertinoDatePickerMode.MONTH_YEAR`, + both `CupertinoDatePickerDateOrder.DAY_MONTH_YEAR` and + `CupertinoDatePickerDateOrder.MONTH_DAY_YEAR` will result in the month|year order. + """ + + on_change: OptionalControlEventHandler["CupertinoDatePicker"] = None + """ + Fires when the selected date and/or time changes. Will not fire if the new + selected value is not valid, or is not in the range of `first_date` and `last_date`. + """ + + def before_update(self): + super().before_update() + + # Normalize value to datetime in case it's a date + if isinstance(self.value, date) and not isinstance(self.value, datetime): + value = datetime.combine(self.value, datetime.min.time()) + else: + value = self.value + + assert self.item_extent > 0, "item_extent must be strictly greater than 0" + assert ( + self.minute_interval > 0 and 60 % self.minute_interval == 0 + ), "minute_interval must be a positive integer factor of 60" + + if self.date_picker_mode == CupertinoDatePickerMode.DATE_AND_TIME: + if self.first_date: + assert ( + value >= self.first_date + ), f"value ({value}) can't be before first_date ({self.first_date})" + if self.last_date: + assert ( + value <= self.last_date + ), f"value ({value}) can't be after last_date ({self.last_date})" + + if self.date_picker_mode in [ + CupertinoDatePickerMode.DATE, + CupertinoDatePickerMode.MONTH_YEAR, + ]: + assert ( + 1 <= self.minimum_year <= value.year + ), f"value.year ({value.year}) can't be less than minimum_year " + f"({self.minimum_year})" + + if self.maximum_year: + assert ( + value.year <= self.maximum_year + ), f"value.year ({value.year}) can't be greater than maximum_year " + f"({self.maximum_year})" + + if self.first_date: + assert ( + value >= self.first_date + ), f"value ({value}) can't be before first_date ({self.first_date})" + + if self.last_date: + assert ( + value <= self.last_date + ), f"value ({value}) can't be after last_date ({self.last_date})" + + if self.date_picker_mode != CupertinoDatePickerMode.DATE: + assert ( + not self.show_day_of_week + ), "show_day_of_week is only supported in CupertinoDatePickerMode.DATE mode" + + assert ( + value.minute % self.minute_interval == 0 + ), f"value.minute ({value.minute}) must be divisible by minute_interval " + f"({self.minute_interval})" diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_dialog_action.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_dialog_action.py new file mode 100644 index 000000000..8d14d0fcc --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_dialog_action.py @@ -0,0 +1,57 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.text_style import TextStyle +from flet.controls.types import StrOrControl + +__all__ = ["CupertinoDialogAction"] + + +@control("CupertinoDialogAction") +class CupertinoDialogAction(Control): + """ + A button typically used in a CupertinoAlertDialog. + + Online docs: https://flet.dev/docs/controls/cupertinodialogaction + """ + + content: StrOrControl + """ + A Control representing custom button content. + """ + + default: bool = False + """ + If set to True, the button will have bold text. More than one action can have + this property set to True in CupertinoAlertDialog. + + Defaults to `False`. + """ + + destructive: bool = False + """ + If set to True, the button's text color will be red. Use it for actions that + destroy objects, such as an delete that deletes an email etc. + + Defaults to `False`. + """ + + text_style: Optional[TextStyle] = None + """ + The text style to use for text on the button. + + Value is of type [`TextStyle`](https://flet.dev/docs/reference/types/textstyle). + """ + + on_click: OptionalControlEventHandler["CupertinoDialogAction"] = None + """ + Fires when a user clicks the button. + """ + + def before_update(self): + super().before_update() + assert isinstance(self.content, str) or self.content.visible, ( + "content must be a string or a visible Control" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_filled_button.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_filled_button.py new file mode 100644 index 000000000..5815fa4ee --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_filled_button.py @@ -0,0 +1,13 @@ +from flet.controls.base_control import control +from flet.controls.cupertino.cupertino_button import CupertinoButton + +__all__ = ["CupertinoFilledButton"] + + +@control("CupertinoFilledButton") +class CupertinoFilledButton(CupertinoButton): + """ + An iOS-style button filled with default background color. + + Online docs: https://flet.dev/docs/controls/cupertinofilledbutton + """ diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_icons.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_icons.py similarity index 99% rename from sdk/python/packages/flet/src/flet/core/cupertino_icons.py rename to sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_icons.py index 28b1ea9f2..e930b6c57 100644 --- a/sdk/python/packages/flet/src/flet/core/cupertino_icons.py +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_icons.py @@ -8,21 +8,24 @@ import random from enum import Enum -from typing import Dict, List, Optional +from typing import Optional + +__all__ = ["CupertinoIcons"] class CupertinoIcons(str, Enum): @staticmethod def random( - exclude: Optional[List["CupertinoIcons"]] = None, - weights: Optional[Dict["CupertinoIcons", int]] = None, + exclude: Optional[list["CupertinoIcons"]] = None, + weights: Optional[dict["CupertinoIcons", int]] = None, ) -> Optional["CupertinoIcons"]: """ Selects a random icon, with optional exclusions and weights. Args: exclude: A list of icons members to exclude from the selection. - weights: A dictionary mapping icon members to their respective weights for weighted random selection. + weights: A dictionary mapping icon members to their respective weights for + weighted random selection. Returns: A randomly selected icon, or None if all members are excluded. diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_list_tile.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_list_tile.py new file mode 100644 index 000000000..51fce74b2 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_list_tile.py @@ -0,0 +1,132 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.padding import OptionalPaddingValue +from flet.controls.types import ( + IconValueOrControl, + OptionalColorValue, + OptionalNumber, + StrOrControl, + UrlTarget, +) + +__all__ = ["CupertinoListTile"] + + +@control("CupertinoListTile") +class CupertinoListTile(ConstrainedControl): + """ + An iOS-style list tile. The CupertinoListTile is a Cupertino equivalent of Material + ListTile. + + Online docs: https://flet.dev/docs/controls/cupertinolisttile + """ + + title: StrOrControl + """ + A `Control` to display as primary content of the list tile. + + Typically a [`Text`](https://flet.dev/docs/controls/text) control. + """ + + subtitle: Optional[StrOrControl] = None + """ + Additional content displayed below the title. + + Typically a [`Text`](https://flet.dev/docs/controls/text) control. + """ + + leading: Optional[IconValueOrControl] = None + """ + A `Control` to display before the `title`. + """ + + trailing: Optional[IconValueOrControl] = None + """ + A `Control` to display after the title. + + Typically an [`Icon`](https://flet.dev/docs/controls/icon) control. + """ + + bgcolor: OptionalColorValue = None + """ + The list tile's background [color](https://flet.dev/docs/reference/colors). + """ + + bgcolor_activated: OptionalColorValue = None + """ + The list tile's background [color](https://flet.dev/docs/reference/colors) + after the tile was tapped. + """ + + padding: OptionalPaddingValue = None + """ + The tile's internal padding. Insets a CupertinoListTile's contents: its + `leading`, `title`, `subtitle`, `additional_info` and `trailing` controls. + + Padding is an instance of + [`Padding`](https://flet.dev/docs/reference/types/padding) class. + """ + + url: Optional[str] = None + """ + The URL to open when the list tile is clicked. If registered, `on_click` + event is fired after that. + """ + + url_target: Optional[UrlTarget] = None + """ + Where to open URL in the web mode. + + Value is of type [`UrlTarget`](https://flet.dev/docs/reference/types/urltarget) + and defaults to `UrlTarget.BLANK`. + """ + + toggle_inputs: bool = False + """ + Whether clicking on a list tile should toggle the state of `Radio`, `Checkbox` + or `Switch` inside the tile. Default is `False`. + """ + + additional_info: Optional[StrOrControl] = None + """ + A `Control` to display on the right of the list tile, before `trailing`. + + Similar to `subtitle`, an `additional_info` is used to display additional + information. Usually a [`Text`](https://flet.dev/docs/controls/text) control. + """ + + leading_size: OptionalNumber = None + """ + Used to constrain the width and height of `leading` control. + + Defaults to `30.0`, if `notched=True`, else `28.0`. + """ + + leading_to_title: OptionalNumber = None + """ + The horizontal space between `leading` and `title`. + + Defaults to `12.0`, if `notched=True`, else `16.0`. + """ + + notched: bool = False + """ + If `True`, list tile will be created in an "Inset Grouped" form, known from + either iOS Notes or Reminders app. + + Defaults to `False`. + """ + + on_click: OptionalControlEventHandler["CupertinoListTile"] = None + """ + Fires when a user clicks or taps the list tile. + """ + + def before_update(self): + super().before_update() + assert isinstance(self.title, str) or self.title.visible, ( + "title must be a string or a visible Control" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_navigation_bar.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_navigation_bar.py new file mode 100644 index 000000000..13adc0acd --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_navigation_bar.py @@ -0,0 +1,80 @@ +from dataclasses import field +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.border import Border +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.cupertino.cupertino_colors import CupertinoColors +from flet.controls.material.navigation_bar import NavigationBarDestination +from flet.controls.types import ( + ColorValue, + Number, + OptionalColorValue, +) + +__all__ = ["CupertinoNavigationBar"] + + +@control("CupertinoNavigationBar") +class CupertinoNavigationBar(ConstrainedControl): + """ + An iOS-styled bottom navigation tab bar. + + Navigation bars offer a persistent and convenient way to switch between primary + destinations in an app. + + Online docs: https://flet.dev/docs/controls/cupertinonavigationbar + """ + + destinations: list[NavigationBarDestination] = field(default_factory=list) + """ + Defines the appearance of the button items that are arrayed within the navigation + bar. + + The value must be a list of two or more + [`NavigationBarDestination`](https://flet.dev/docs/controls/navigationbar#navigationbardestination-properties) + instances. + """ + + selected_index: int = 0 + """ + The index into `destinations` for the current selected `NavigationBarDestination`. + """ + + bgcolor: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the navigation bar itself. + """ + + active_color: OptionalColorValue = None + """ + The foreground [color](https://flet.dev/docs/reference/colors) of the icon and + title of the selected destination. + """ + + inactive_color: ColorValue = CupertinoColors.INACTIVE_GRAY + """ + The foreground [color](https://flet.dev/docs/reference/colors) of the icon and + title of the unselected destinations. + """ + + border: Optional[Border] = None + """ + Defines the border of this navigation bar. + + The value is an instance of + [`Border`](https://flet.dev/docs/reference/types/border) class. + """ + + icon_size: Number = 30 + """ + The size of all destination icons. + + Defaults to `30`. + """ + + on_change: OptionalControlEventHandler["CupertinoNavigationBar"] = None + """ + Fires when selected destination changed. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_picker.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_picker.py new file mode 100644 index 000000000..c18009da3 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_picker.py @@ -0,0 +1,116 @@ +from dataclasses import field +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.cupertino.cupertino_colors import CupertinoColors +from flet.controls.types import ( + ColorValue, + Number, + OptionalColorValue, +) + +__all__ = ["CupertinoPicker"] + + +@control("CupertinoPicker") +class CupertinoPicker(ConstrainedControl): + """ + An iOS-styled picker. + + Online docs: https://flet.dev/docs/controls/cupertinopicker + """ + + controls: list[Control] = field(default_factory=list) + """ + A list of controls representing items in this picker. + """ + + item_extent: Number = 32.0 + """ + The uniform height of all children. + + Defaults to `32`. + """ + + selected_index: int = 0 + """ + The index (starting from 0) of the selected item in the `controls` list. + """ + + bgcolor: OptionalColorValue = None + """ + The background [color](https://flet.dev/docs/reference/colors) of the timer picker. + """ + + use_magnifier: bool = False + """ + Whether to use the magnifier for the center item of the wheel. + """ + + looping: bool = False + """ + If `True`, children on a wheel can be scrolled in a loop. + + Defaults to `False`. + """ + + magnification: Number = 1.0 + """ + The zoomed-in rate of the magnifier, if it is used. + + If the value is greater than `1.0`, the item in the center will be zoomed in by that + rate, and it will also be rendered as flat, not cylindrical like the rest of the + list. The item will be zoomed-out if magnification is less than `1.0`. + + Defaults to `1.0` - normal. + """ + + squeeze: Number = 1.45 + """ + The angular compactness of the children on the wheel. + """ + + diameter_ratio: Number = 1.07 + """ + Relative ratio between this picker's height and the simulated cylinder's diameter. + + Smaller values create more pronounced curvatures in the scrollable wheel. + """ + + off_axis_fraction: Number = 0.0 + """ + How much the wheel is horizontally off-center, as a fraction of its width. + """ + + selection_overlay: Optional[Control] = None + """ + A control overlaid on the picker to highlight the selected entry, centered and + matching the height of the center row. + + Defaults to a rounded rectangle in iOS 14 style with + `default_selection_overlay_bgcolor` as background color. + """ + + default_selection_overlay_bgcolor: ColorValue = CupertinoColors.TERTIARY_SYSTEM_FILL + """ + The default background [color](https://flet.dev/docs/reference/colors) of the + `selection_overlay`. + + Defaults to `CupertinoColors.TERTIARY_SYSTEM_FILL`. + """ + + on_change: OptionalControlEventHandler["CupertinoPicker"] = None + """ + Fires when the selection changes. + """ + + def before_update(self): + super().before_update() + assert self.squeeze > 0, "squeeze must be strictly greater than 0" + assert self.magnification > 0, "magnification must be strictly greater than 0" + assert self.item_extent is None or self.item_extent > 0, ( + "item_extent must be strictly greater than 0" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_radio.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_radio.py new file mode 100644 index 000000000..2089c4396 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_radio.py @@ -0,0 +1,103 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.types import ( + LabelPosition, + MouseCursor, + OptionalColorValue, +) + +__all__ = ["CupertinoRadio"] + + +@control("CupertinoRadio") +class CupertinoRadio(ConstrainedControl): + """ + Radio buttons let people select a single option from two or more choices. + + Online docs: https://flet.dev/docs/controls/cupertinoradio + """ + + label: Optional[str] = None + """ + The clickable label to display on the right of a Radio. + """ + + value: str = "" + """ + The value to set to containing `RadioGroup` when the radio is selected. + """ + + label_position: LabelPosition = LabelPosition.RIGHT + """ + The position of the label relative to the radio. + + Value is of type + [LabelPosition](https://flet.dev/docs/reference/types/labelposition) and + defaults to `LabelPosition.RIGHT`. + """ + + fill_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) that fills the radio. + """ + + active_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used to fill this radio + when it is selected. + """ + + inactive_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used to fill this radio + when it is not selected. + + Defaults to `colors.WHITE`. + """ + + autofocus: bool = False + """ + True if the control will be selected as the initial focus. + + If there is more than one control on a page with autofocus set, then the first + one added to the page will get focus. + """ + + use_checkmark_style: bool = False + """ + Defines whether the radio displays in a checkbox style or the default radio style. + + Defaults to `False`. + """ + + toggleable: bool = False + """ + Set to `True` if this radio button is allowed to be returned to an indeterminate + state by selecting it again when selected. + + Defaults to `False`. + """ + + focus_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) for the radio's border + when it has the input focus. + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + TBD + """ + + on_focus: OptionalControlEventHandler["CupertinoRadio"] = None + """ + Fires when the control has received focus. + """ + + on_blur: OptionalControlEventHandler["CupertinoRadio"] = None + """ + Fires when the control has lost focus. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_segmented_button.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_segmented_button.py new file mode 100644 index 000000000..5db555f6b --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_segmented_button.py @@ -0,0 +1,89 @@ +from dataclasses import field + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.padding import OptionalPaddingValue +from flet.controls.types import OptionalColorValue + +__all__ = ["CupertinoSegmentedButton"] + + +@control("CupertinoSegmentedButton") +class CupertinoSegmentedButton(ConstrainedControl): + """ + An iOS-style segmented button. + + Online docs: https://flet.dev/docs/controls/cupertinosegmentedbutton + """ + + controls: list[Control] = field(default_factory=list) + """ + A list of `Control`s to display as segments inside the CupertinoSegmentedButton. + """ + + selected_index: int = 0 + """ + The index (starting from 0) of the selected segment in the `controls` list. + """ + + selected_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the button when it is + selected. + """ + + unselected_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the button when it is not + selected. + """ + + border_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the button's border. + """ + + padding: OptionalPaddingValue = None + """ + The button's padding. + + Padding value is an instance of + [Padding](https://flet.dev/docs/reference/types/padding) class. + """ + + click_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used to fill the background + of this control when temporarily interacting with through a long press or drag. + + Defaults to the `selected_color` with 20% opacity. + """ + + disabled_color: OptionalColorValue = None + """ + TBD + """ + + disabled_text_color: OptionalColorValue = None + """ + TBD + """ + + on_change: OptionalControlEventHandler["CupertinoSegmentedButton"] = None + """ + Fires when the state of the button is changed - when one of the `controls` is + clicked. + """ + + def before_update(self): + super().before_update() + assert len(self.controls) >= 2, ( + "controls must contain minimum two visible controls" + ) + if not (0 <= self.selected_index < len(self.controls)): + raise IndexError( + f"selected_index {self.selected_index} is out of range. " + f"Expected a value between 0 and {len(self.controls) - 1}." + ) diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_slider.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_slider.py new file mode 100644 index 000000000..fa0293248 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_slider.py @@ -0,0 +1,115 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.types import ( + Number, + OptionalColorValue, + OptionalNumber, +) + +__all__ = ["CupertinoSlider"] + + +@control("CupertinoSlider") +class CupertinoSlider(ConstrainedControl): + """ + An iOS-type slider. + + It provides a visual indication of adjustable content, as well as the current + setting in the total range of content. + + Use a slider when you want people to set defined values (such as volume or + brightness), or when people would benefit from instant feedback on the effect of + setting changes. + + Online docs: https://flet.dev/docs/controls/cupertinoslider + """ + + value: OptionalNumber = None + """ + The currently selected value for this slider. + + The slider's thumb is drawn at a position that corresponds to this value. + """ + + min: Number = 0.0 + """ + The minimum value the user can select. + + Defaults to `0.0`. Must be less than or equal to `max`. + + If the `max` is equal to the `min`, then the slider is disabled. + """ + + max: Number = 1.0 + """ + The maximum value the user can select. + + Defaults to `1.0`. Must be greater than or equal to `min`. + + If the `max` is equal to the `min`, then the slider is disabled. + """ + + divisions: Optional[int] = None + """ + The number of discrete divisions. + + If not set, the slider is continuous. + """ + + round: int = 0 + """ + TBD + """ + + active_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use for the portion of the + slider track that is active. + + The "active" side of the slider is the side between the thumb and the minimum + value. + """ + + thumb_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the thumb. + """ + + on_change: OptionalControlEventHandler["CupertinoSlider"] = None + """ + Fires when the state of the Slider is changed. + """ + + on_change_start: OptionalControlEventHandler["CupertinoSlider"] = None + """ + Fires when the user starts selecting a new value for the slider. + """ + + on_change_end: OptionalControlEventHandler["CupertinoSlider"] = None + """ + Fires when the user is done selecting a new value for the slider. + """ + + on_focus: OptionalControlEventHandler["CupertinoSlider"] = None + """ + Fires when the control has received focus. + """ + + on_blur: OptionalControlEventHandler["CupertinoSlider"] = None + """ + Fires when the control has lost focus. + """ + + def before_update(self): + super().before_update() + self.value = self.value if self.value is not None else self.min + assert self.min <= self.max, "min must be less than or equal to max" + assert self.value is None or (self.value >= self.min), ( + "value must be greater than or equal to min" + ) + assert self.value is None or (self.value <= self.max), ( + "value must be less than or equal to max" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_sliding_segmented_button.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_sliding_segmented_button.py new file mode 100644 index 000000000..8d6b2d7ac --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_sliding_segmented_button.py @@ -0,0 +1,72 @@ +from dataclasses import field + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.cupertino.cupertino_colors import CupertinoColors +from flet.controls.padding import Padding, PaddingValue +from flet.controls.types import ( + ColorValue, + OptionalColorValue, +) + +__all__ = ["CupertinoSlidingSegmentedButton"] + + +@control("CupertinoSlidingSegmentedButton") +class CupertinoSlidingSegmentedButton(ConstrainedControl): + """ + A CupertinoSlidingSegmentedButton. + + Online docs: https://flet.dev/docs/controls/cupertinoslidingsegmentedbutton + """ + + controls: list[Control] = field(default_factory=list) + """ + A list of `Control`s to display as segments inside the CupertinoSegmentedButton. + Must have at least 2 items. + """ + + selected_index: int = 0 + """ + The index (starting from 0) of the selected segment in the `controls` list. + """ + + bgcolor: ColorValue = CupertinoColors.TERTIARY_SYSTEM_FILL + """ + The background [color](https://flet.dev/docs/reference/colors) of the button. + """ + + thumb_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the button when it is not + selected. + """ + + padding: PaddingValue = field( + default_factory=lambda: Padding.symmetric(vertical=2, horizontal=3) + ) + """ + The button's padding. + + Padding value is an instance of + [Padding](https://flet.dev/docs/reference/types/padding) class. + """ + + proportional_width: bool = False + """ + TBD + """ + + on_change: OptionalControlEventHandler["CupertinoSlidingSegmentedButton"] = None + """ + Fires when the state of the button is changed - when one of the `controls` is + clicked. + """ + + def before_update(self): + super().before_update() + assert sum(c.visible for c in self.controls) >= 2, ( + "controls must have at minimum two visible Controls" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_switch.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_switch.py new file mode 100644 index 000000000..a6fbfd004 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_switch.py @@ -0,0 +1,151 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.control_state import ControlStateValue +from flet.controls.types import ( + ColorValue, + IconValue, + LabelPosition, + OptionalColorValue, + OptionalNumber, +) + +__all__ = ["CupertinoSwitch"] + + +@control("CupertinoSwitch") +class CupertinoSwitch(ConstrainedControl): + """ + An iOS-style switch. Used to toggle the on/off state of a single setting. + + Online docs: https://flet.dev/docs/controls/cupertinoswitch + """ + + label: Optional[str] = None + """ + The clickable label to display on the right of the switch. + """ + + value: bool = False + """ + Current value of the switch. + """ + + label_position: LabelPosition = LabelPosition.RIGHT + """ + The position of the label relative to the switch. + + Value is of type + [LabelPosition](https://flet.dev/docs/reference/types/labelposition) and defaults + to `LabelPosition.RIGHT`. + """ + + thumb_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the switch's thumb. + """ + + focus_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use for the focus highlight + for keyboard interactions. + """ + + autofocus: bool = False + """ + True if the control will be selected as the initial focus. + + If there is more than one control on a page with autofocus set, then the first one + added to the page will get focus. + """ + + on_label_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use for the accessibility + label when the switch is on. + """ + + off_label_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use for the accessibility + label when the switch is off. + """ + + active_thumb_image: Optional[str] = None + """ + An image to use on the thumb of this switch when the switch is on. + + Can be a local file path or URL. + """ + + inactive_thumb_image: Optional[str] = None + """ + An image to use on the thumb of this switch when the switch is off. + + Can be a local file path or URL. + """ + + active_track_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use on the track when this + switch is on. + """ + + inactive_thumb_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use on the thumb when this + switch is off. + + If `None`, defaults to `thumb_color`, or `CupertinoColors.WHITE` if `thumb_color` + is also `None`. + """ + + inactive_track_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use on the track when this + switch is off. + """ + + track_outline_color: Optional[ControlStateValue[ColorValue]] = None + """ + The outline [color](https://flet.dev/docs/reference/colors) of this switch's track + in various + [ControlState](https://flet.dev/docs/reference/types/controlstate) states. + """ + + track_outline_width: Optional[ControlStateValue[OptionalNumber]] = None + """ + The outline width of this switch's track in all or specific + [ControlState](https://flet.dev/docs/reference/types/controlstate) states. + """ + + thumb_icon: Optional[ControlStateValue[IconValue]] = None + """ + The icon of this Switch's thumb in various + [ControlState](https://flet.dev/docs/reference/types/controlstate) states. + + Supported values: `SELECTED`, `HOVERED`, `DISABLED`, `FOCUSED`, `DEFAULT`. + """ + + on_change: OptionalControlEventHandler["CupertinoSwitch"] = None + """ + Fires when the state of the switch is changed. + """ + + on_focus: OptionalControlEventHandler["CupertinoSwitch"] = None + """ + Fires when the control has received focus. + """ + + on_blur: OptionalControlEventHandler["CupertinoSwitch"] = None + """ + Fires when the control has lost focus. + """ + + on_image_error: OptionalControlEventHandler["CupertinoSwitch"] = None + """ + Fires when the image (`active_thumb_image` or `inactive_thumb_image`) fails to + load. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_textfield.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_textfield.py new file mode 100644 index 000000000..ef6bd873e --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_textfield.py @@ -0,0 +1,122 @@ +from enum import Enum +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.box import DecorationImage, OptionalShadowValue +from flet.controls.gradients import Gradient +from flet.controls.material.textfield import TextField +from flet.controls.padding import OptionalPaddingValue +from flet.controls.text_style import TextStyle +from flet.controls.types import BlendMode + +__all__ = ["CupertinoTextField", "VisibilityMode"] + + +class VisibilityMode(Enum): + NEVER = "never" + EDITING = "editing" + NOT_EDITING = "notEditing" + ALWAYS = "always" + + +@control("CupertinoTextField") +class CupertinoTextField(TextField): + """ + An iOS-style text field. + + Online docs: https://flet.dev/docs/controls/cupertinotextfield + """ + + placeholder_text: Optional[str] = None + """ + A lighter colored placeholder hint that appears on the first line of the text + field when the text entry is empty. + + Defaults to an empty string. + """ + + placeholder_style: Optional[TextStyle] = None + """ + The [TextStyle](https://flet.dev/docs/reference/types/textstyle) to use for + `placeholder_text`. + """ + + gradient: Optional[Gradient] = None + """ + Configures gradient background. + + Value is of type [Gradient](https://flet.dev/docs/reference/types/gradient). + """ + + blend_mode: Optional[BlendMode] = None + """ + The blend mode applied to the `color` or `gradient` background. + + Value is of type [BlendMode](https://flet.dev/docs/reference/types/blendmode) and + defaults to `BlendMode.MODULATE`. + """ + + shadow: OptionalShadowValue = None + """ + A list of shadows behind the text field. + """ + + prefix_visibility_mode: VisibilityMode = VisibilityMode.ALWAYS + """ + Defines the visibility of the `prefix` control based on the state of text entry. + + Has no effect if `prefix` is not specified. + + Value is of type + [VisibilityMode](https://flet.dev/docs/reference/types/visibilitymode) and + defaults to `VisibilityMode.ALWAYS`. + """ + + suffix_visibility_mode: VisibilityMode = VisibilityMode.ALWAYS + """ + Defines the visibility of the `suffix` control based on the state of text entry. + + Has no effect if `suffix` is not specified. + + Value is of type + [VisibilityMode](https://flet.dev/docs/reference/types/visibilitymode) and + defaults to `VisibilityMode.ALWAYS`. + """ + + clear_button_visibility_mode: VisibilityMode = VisibilityMode.NEVER + """ + Defines the visibility of the clear button based on the state of text entry. + + Will appear only if no `suffix` is provided. + + Value is of type + [VisibilityMode](https://flet.dev/docs/reference/types/visibilitymode) and + defaults to `VisibilityMode.NEVER`. + """ + + clear_button_semantics_label: Optional[str] = None + """ + The semantic label for the clear button used by screen readers. + + This will be used by screen reading software to identify the clear button widget. + + Defaults to `"Clear"`. + """ + + image: Optional[DecorationImage] = None + """ + An image to paint above the `bgcolor` or `gradient`. + + Value is of type + [DecorationImage](https://flet.dev/docs/reference/types/decorationimage). + """ + + padding: OptionalPaddingValue = None + """ + The padding around the text entry area between the `prefix` and `suffix` or the + clear button when `clear_button_mode` is not `VisibilityMode.NEVER`. + + Value is of type + [Padding](https://flet.dev/docs/reference/types/padding) and defaults to padding + of `7` pixels on all sides. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_timer_picker.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_timer_picker.py new file mode 100644 index 000000000..dff55d8f9 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_timer_picker.py @@ -0,0 +1,111 @@ +from dataclasses import field +from enum import Enum + +from flet.controls.alignment import Alignment +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.duration import Duration, DurationValue +from flet.controls.types import Number, OptionalColorValue + +__all__ = ["CupertinoTimerPicker", "CupertinoTimerPickerMode"] + + +class CupertinoTimerPickerMode(Enum): + HOUR_MINUTE = "hm" + HOUR_MINUTE_SECONDS = "hms" + MINUTE_SECONDS = "ms" + + +@control("CupertinoTimerPicker") +class CupertinoTimerPicker(ConstrainedControl): + """ + A countdown timer picker in iOS style. + + It can show a countdown duration with hour, minute and second spinners. The + duration is bound between 0 and 23 hours 59 minutes 59 seconds. + + Online docs: https://flet.dev/docs/controls/cupertinotimerpicker + """ + + value: DurationValue = field(default_factory=lambda: Duration()) + """ + The initial duration in seconds of the countdown timer. + + Defaults to `0`. + """ + + alignment: Alignment = field(default_factory=lambda: Alignment.center()) + """ + Defines how the timer picker should be positioned within its parent. + + Value is of type [Alignment](https://flet.dev/docs/reference/types/alignment) and + defaults to `alignment.center`. + """ + + second_interval: int = 1 + """ + The granularity of the second spinner. + + Must be a positive integer factor of 60. Defaults to `1`. + """ + + minute_interval: int = 1 + """ + The granularity of the minute spinner. + + Must be a positive integer factor of 60. Defaults to `1`. + """ + + mode: CupertinoTimerPickerMode = CupertinoTimerPickerMode.HOUR_MINUTE_SECONDS + """ + The mode of the timer picker. + + Value is of type + [CupertinoTimerPickerMode](https://flet.dev/docs/reference/types/cupertinotimerpickermode) + and defaults to `CupertinoTimerPickerMode.HOUR_MINUTE_SECOND`. + """ + + bgcolor: OptionalColorValue = None + """ + The background [color](https://flet.dev/docs/reference/colors) of the timer picker. + """ + + item_extent: Number = 32.0 + """ + The uniform height of all children. + + Defaults to `32`. + """ + + on_change: OptionalControlEventHandler["CupertinoTimerPicker"] = None + """ + Fires when the timer duration changes. + """ + + def before_update(self): + super().before_update() + value = ( + self.value + if isinstance(self.value, Duration) + else Duration(seconds=self.value) + ) + assert value >= Duration(), "value must be a non-negative duration" + assert value < Duration(hours=24), "value must be strictly less than 24 hours" + assert self.minute_interval > 0 and 60 % self.minute_interval == 0, ( + f"minute_interval ({self.minute_interval}) must be a positive integer " + ) + "factor of 60" + assert self.second_interval > 0 and 60 % self.second_interval == 0, ( + f"second_interval ({self.second_interval}) must be a positive integer " + ) + "factor of 60" + assert value.in_minutes % self.minute_interval == 0, ( + f"value ({value.in_minutes} minutes) must be a multiple of minute_interval " + ) + f"({self.minute_interval})" + assert value.in_seconds % self.second_interval == 0, ( + f"value ({value.in_seconds} seconds) must be a multiple of second_interval " + ) + f"({self.second_interval})" + assert self.item_extent > 0, "item_extent must be strictly greater than 0" diff --git a/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_tinted_button.py b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_tinted_button.py new file mode 100644 index 000000000..e99d1d8ea --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/cupertino/cupertino_tinted_button.py @@ -0,0 +1,13 @@ +from flet.controls.base_control import control +from flet.controls.cupertino.cupertino_button import CupertinoButton + +__all__ = ["CupertinoTintedButton"] + + +@control("CupertinoTintedButton") +class CupertinoTintedButton(CupertinoButton): + """ + An iOS-style button filled with default background color. + + Online docs: https://flet.dev/docs/controls/cupertinofilledbutton + """ diff --git a/sdk/python/packages/flet/src/flet/controls/data_view.py b/sdk/python/packages/flet/src/flet/controls/data_view.py new file mode 100644 index 000000000..77495f041 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/data_view.py @@ -0,0 +1,48 @@ +import functools +import hashlib +import weakref +from typing import Callable + + +# --- Utility to create a hashable signature from args --- +def _hash_args(*args, **kwargs): + try: + # Convert args/kwargs to a string and hash it + sig = repr((args, kwargs)) + return hashlib.sha256(sig.encode()).hexdigest() + except Exception: + # fallback to id-based hash if unhashable + return str(id(args)) + str(id(kwargs)) + + +# --- Freeze controls in the returned structure --- +def _freeze_controls(control): + if isinstance(control, list): + return [_freeze_controls(c) for c in control] + elif isinstance(control, dict): + return {k: _freeze_controls(v) for k, v in control.items()} + elif hasattr(control, "__dict__"): # assume it's a control + object.__setattr__(control, "_frozen", True) + return control + + +# --- Main decorator --- +def data_view(fn: Callable): + cache = weakref.WeakValueDictionary() + + @functools.wraps(fn) + def wrapper(*args, **kwargs): + key = _hash_args(*args, **kwargs) + + if key in cache: + return cache[key] + + result = fn(*args, **kwargs) + if result is not None: + _freeze_controls(result) + cache[key] = result + elif key in cache: + del cache[key] + return result + + return wrapper diff --git a/sdk/python/packages/flet/src/flet/controls/dialog_control.py b/sdk/python/packages/flet/src/flet/controls/dialog_control.py new file mode 100644 index 000000000..1b5589a23 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/dialog_control.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.control_event import OptionalControlEventHandler + + +@dataclass(kw_only=True) +class DialogControl(AdaptiveControl): + """ + TBD + """ + + open: bool = False + """ + Set to `True` to display a dialog. + + Value is of type `bool` and defaults to `False`. + """ + + on_dismiss: OptionalControlEventHandler["DialogControl"] = None + """ + Fires when dialog is dismissed. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/duration.py b/sdk/python/packages/flet/src/flet/controls/duration.py new file mode 100644 index 000000000..ba5f6f26c --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/duration.py @@ -0,0 +1,238 @@ +from dataclasses import dataclass +from datetime import date, datetime +from typing import Optional, Union +from flet.controls.types import Number + +__all__ = [ + "Duration", + "DurationValue", + "OptionalDurationValue", + "MICROSECONDS_PER_MILLISECOND", + "MICROSECONDS_PER_SECOND", + "MICROSECONDS_PER_MINUTE", + "MICROSECONDS_PER_HOUR", + "MICROSECONDS_PER_DAY", + "DateTimeValue", + "OptionalDateTimeValue", + "OptionalDuration", +] + +MICROSECONDS_PER_MILLISECOND = 1_000 +MICROSECONDS_PER_SECOND = 1_000_000 +MICROSECONDS_PER_MINUTE = 60 * MICROSECONDS_PER_SECOND +MICROSECONDS_PER_HOUR = 60 * MICROSECONDS_PER_MINUTE +MICROSECONDS_PER_DAY = 24 * MICROSECONDS_PER_HOUR + + +@dataclass +class Duration: + """ + A span of time, such as 27 days, 4 hours, 12 minutes, and 3 seconds. + + A Duration represents a difference from one point in time to another. The duration + may be "negative" if the difference is from a later time to an earlier. + """ + + microseconds: int = 0 + """ + The number of microseconds in the duration. + """ + + milliseconds: int = 0 + """ + The number of milliseconds in the duration. + """ + + seconds: int = 0 + """ + The number of seconds in the duration. + """ + + minutes: int = 0 + """ + The number of minutes in the duration. + """ + + hours: int = 0 + """ + The number of hours in the duration. + """ + + days: int = 0 + """ + The number of days in the duration. + """ + + # Class methods + + @classmethod + def from_unit( + cls, + *, + days: int = 0, + hours: int = 0, + minutes: int = 0, + seconds: int = 0, + milliseconds: int = 0, + microseconds: int = 0, + ) -> "Duration": + """Creates a Duration from individual time units.""" + total_microseconds = ( + days * MICROSECONDS_PER_DAY + + hours * MICROSECONDS_PER_HOUR + + minutes * MICROSECONDS_PER_MINUTE + + seconds * MICROSECONDS_PER_SECOND + + milliseconds * MICROSECONDS_PER_MILLISECOND + + microseconds + ) + d, rem = divmod(total_microseconds, MICROSECONDS_PER_DAY) + h, rem = divmod(rem, MICROSECONDS_PER_HOUR) + m, rem = divmod(rem, MICROSECONDS_PER_MINUTE) + s, rem = divmod(rem, MICROSECONDS_PER_SECOND) + ms, us = divmod(rem, MICROSECONDS_PER_MILLISECOND) + + return cls( + days=d, hours=h, minutes=m, seconds=s, milliseconds=ms, microseconds=us + ) + + # Properties + + @property + def in_microseconds(self) -> int: + """Returns total duration in microseconds.""" + return ( + self.microseconds + + self.milliseconds * MICROSECONDS_PER_MILLISECOND + + self.seconds * MICROSECONDS_PER_SECOND + + self.minutes * MICROSECONDS_PER_MINUTE + + self.hours * MICROSECONDS_PER_HOUR + + self.days * MICROSECONDS_PER_DAY + ) + + @property + def in_milliseconds(self) -> int: + """Returns total duration in milliseconds.""" + return self.in_microseconds // MICROSECONDS_PER_MILLISECOND + + @property + def in_seconds(self) -> int: + """Returns total duration in seconds.""" + return self.in_microseconds // MICROSECONDS_PER_SECOND + + @property + def in_minutes(self) -> int: + """Returns total duration in minutes.""" + return self.in_microseconds // MICROSECONDS_PER_MINUTE + + @property + def in_hours(self) -> int: + """Returns total duration in hours.""" + return self.in_microseconds // MICROSECONDS_PER_HOUR + + @property + def in_days(self) -> int: + """Returns total duration in days.""" + return self.in_microseconds // MICROSECONDS_PER_DAY + + @property + def is_negative(self) -> bool: + """Returns True if duration is negative.""" + return self.in_microseconds < 0 + + def abs(self) -> "Duration": + """Returns absolute (non-negative) Duration.""" + return Duration.from_unit(microseconds=abs(self.in_microseconds)) + + # Arithmetics + + def __add__(self, other: "Duration") -> "Duration": + """Adds two Duration instances.""" + if not isinstance(other, Duration): + return NotImplemented + return Duration.from_unit( + microseconds=self.in_microseconds + other.in_microseconds + ) + + def __sub__(self, other: "Duration") -> "Duration": + """Subtracts one Duration from another.""" + if not isinstance(other, Duration): + return NotImplemented + return Duration.from_unit( + microseconds=self.in_microseconds - other.in_microseconds + ) + + def __mul__(self, other: Number) -> "Duration": + """Multiplies Duration by a scalar factor.""" + if not isinstance(other, Number): + return Duration.from_unit(microseconds=round(self.in_microseconds * other)) + return NotImplemented + + def __floordiv__(self, quotient: int) -> "Duration": + """Performs floor division on Duration.""" + if quotient == 0: + raise ZeroDivisionError("Division by zero is not possible") + return Duration.from_unit(microseconds=self.in_microseconds // quotient) + + # Comparisons + + def __eq__(self, other) -> bool: + """Checks equality between Durations.""" + if not isinstance(other, Duration): + return False + return self.in_microseconds == other.in_microseconds + + def __lt__(self, other: "Duration") -> bool: + """Checks if Duration is less than another.""" + if not isinstance(other, Duration): + return False + return self.in_microseconds < other.in_microseconds + + def __le__(self, other: "Duration") -> bool: + """Checks if Duration is less than or equal to another.""" + if not isinstance(other, Duration): + return False + return self.in_microseconds <= other.in_microseconds + + def __gt__(self, other: "Duration") -> bool: + """Checks if Duration is greater than another.""" + if not isinstance(other, Duration): + return False + return self.in_microseconds > other.in_microseconds + + def __ge__(self, other: "Duration") -> bool: + """Checks if Duration is greater than or equal to another.""" + if not isinstance(other, Duration): + return False + return self.in_microseconds >= other.in_microseconds + + # Instance Methods + + def copy_with( + self, + *, + microseconds: Optional[int] = None, + milliseconds: Optional[int] = None, + seconds: Optional[int] = None, + minutes: Optional[int] = None, + hours: Optional[int] = None, + days: Optional[int] = None, + ) -> "Duration": + """ + Returns a copy of this `Duration` instance with the given fields replaced + with the new values. + """ + return Duration( + microseconds=microseconds if microseconds is not None else self.microseconds, + milliseconds=milliseconds if milliseconds is not None else self.milliseconds, + seconds=seconds if seconds is not None else self.seconds, + minutes=minutes if minutes is not None else self.minutes, + hours=hours if hours is not None else self.hours, + days=days if days is not None else self.days, + ) + + +OptionalDuration = Optional[Duration] +DurationValue = Union[Duration, int] +OptionalDurationValue = Optional[DurationValue] +DateTimeValue = Union[datetime, date] +OptionalDateTimeValue = Optional[DateTimeValue] diff --git a/sdk/python/packages/flet/src/flet/controls/embed_json_encoder.py b/sdk/python/packages/flet/src/flet/controls/embed_json_encoder.py new file mode 100644 index 000000000..53568f1da --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/embed_json_encoder.py @@ -0,0 +1,41 @@ +import enum +import json + +__all__ = ["EmbedJsonEncoder"] + + +class EmbedJsonEncoder(json.JSONEncoder): + def default(self, obj): + obj_as_dict = self._convert_enums(obj.__dict__) + + # Convert inf to string "inf" to avoid JSON serialization error + for key, value in obj_as_dict.items(): + if value == float("inf"): + obj_as_dict[key] = "inf" + + return obj_as_dict + + def encode(self, o): + return super().encode(self._convert_enums(o)) + + def _convert_enums(self, obj): + if isinstance(obj, dict): + return dict( + map( + lambda item: ( + self._convert_enums( + item[0] + if not isinstance(item[0], enum.Enum) + else item[0].value + ), + self._convert_enums( + item[1] + if not isinstance(item[1], enum.Enum) + else item[1].value + ), + ), + filter(lambda item: item[1] is not None, obj.items()), + ) + ) + else: + return obj diff --git a/sdk/python/packages/flet/src/flet/controls/events.py b/sdk/python/packages/flet/src/flet/controls/events.py new file mode 100644 index 000000000..17582a884 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/events.py @@ -0,0 +1,164 @@ +from dataclasses import dataclass, field +from typing import Optional + +from flet.controls.control_event import ControlEvent, Event, EventControlType +from flet.controls.duration import Duration, OptionalDuration +from flet.controls.types import PointerDeviceType + +__all__ = [ + "TapEvent", + "MultiTapEvent", + "LongPressStartEvent", + "LongPressEndEvent", + "DragStartEvent", + "DragUpdateEvent", + "DragEndEvent", + "ScaleStartEvent", + "ScaleUpdateEvent", + "ScaleEndEvent", + "HoverEvent", + "ScrollEvent", + "PointerEvent", +] + + +@dataclass(kw_only=True) +class TapEvent(Event[EventControlType]): + kind: Optional[str] = field(default=None, metadata={"data_field": "k"}) + local_x: Optional[float] = field(default=None, metadata={"data_field": "lx"}) + local_y: Optional[float] = field(default=None, metadata={"data_field": "ly"}) + global_x: Optional[float] = field(default=None, metadata={"data_field": "gx"}) + global_y: Optional[float] = field(default=None, metadata={"data_field": "gy"}) + + +@dataclass(kw_only=True) +class MultiTapEvent(ControlEvent): + correct_touches: bool = field(metadata={"data_field": "ct"}) + + +@dataclass(kw_only=True) +class LongPressStartEvent(Event[EventControlType]): + local_x: float = field(metadata={"data_field": "lx"}) + local_y: float = field(metadata={"data_field": "ly"}) + global_x: float = field(metadata={"data_field": "gx"}) + global_y: float = field(metadata={"data_field": "gy"}) + + +@dataclass(kw_only=True) +class LongPressEndEvent(Event[EventControlType]): + local_x: float = field(metadata={"data_field": "lx"}) + local_y: float = field(metadata={"data_field": "ly"}) + global_x: float = field(metadata={"data_field": "gx"}) + global_y: float = field(metadata={"data_field": "gy"}) + velocity_x: float = field(metadata={"data_field": "vx"}) + velocity_y: float = field(metadata={"data_field": "vy"}) + + +@dataclass(kw_only=True) +class DragStartEvent(Event[EventControlType]): + kind: PointerDeviceType = field(metadata={"data_field": "k"}) + local_x: float = field(metadata={"data_field": "lx"}) + local_y: float = field(metadata={"data_field": "ly"}) + global_x: float = field(metadata={"data_field": "gx"}) + global_y: float = field(metadata={"data_field": "gy"}) + timestamp: OptionalDuration = field(default=None, metadata={"data_field": "ts"}) + + +@dataclass(kw_only=True) +class DragUpdateEvent(Event[EventControlType]): + local_x: float = field(metadata={"data_field": "lx"}) + local_y: float = field(metadata={"data_field": "ly"}) + global_x: float = field(metadata={"data_field": "gx"}) + global_y: float = field(metadata={"data_field": "gy"}) + delta_x: float = field(metadata={"data_field": "dx"}) + delta_y: float = field(metadata={"data_field": "dy"}) + primary_delta: Optional[float] = field(default=None, metadata={"data_field": "pd"}) + timestamp: OptionalDuration = field(default=None, metadata={"data_field": "ts"}) + + +@dataclass(kw_only=True) +class DragEndEvent(Event[EventControlType]): + local_x: float = field(metadata={"data_field": "lx"}) + local_y: float = field(metadata={"data_field": "ly"}) + global_x: float = field(metadata={"data_field": "gx"}) + global_y: float = field(metadata={"data_field": "gy"}) + velocity_x: float = field(metadata={"data_field": "vx"}) + velocity_y: float = field(metadata={"data_field": "vy"}) + primary_velocity: Optional[float] = field( + default=None, metadata={"data_field": "pv"} + ) + + +@dataclass(kw_only=True) +class ScaleStartEvent(Event[EventControlType]): + focal_point_x: float = field(metadata={"data_field": "fpx"}) + focal_point_y: float = field(metadata={"data_field": "fpy"}) + local_focal_point_x: float = field(metadata={"data_field": "lfpx"}) + local_focal_point_y: float = field(metadata={"data_field": "lfpy"}) + pointer_count: int = field(metadata={"data_field": "pc"}) + timestamp: OptionalDuration = field(metadata={"data_field": "ts"}) + + +@dataclass(kw_only=True) +class ScaleEndEvent(Event[EventControlType]): + pointer_count: int = field(metadata={"data_field": "pc"}) + velocity_x: float = field(metadata={"data_field": "vx"}) + velocity_y: float = field(metadata={"data_field": "vy"}) + + +@dataclass(kw_only=True) +class ScaleUpdateEvent(Event[EventControlType]): + focal_point_x: float = field(metadata={"data_field": "fpx"}) + focal_point_y: float = field(metadata={"data_field": "fpy"}) + focal_point_delta_x: float = field(metadata={"data_field": "fpdx"}) + focal_point_delta_y: float = field(metadata={"data_field": "fpdy"}) + local_focal_point_x: float = field(metadata={"data_field": "lfpx"}) + local_focal_point_y: float = field(metadata={"data_field": "lfpy"}) + pointer_count: int = field(metadata={"data_field": "pc"}) + horizontal_scale: float = field(metadata={"data_field": "hs"}) + vertical_scale: float = field(metadata={"data_field": "vs"}) + scale: float = field(metadata={"data_field": "s"}) + rotation: float = field(metadata={"data_field": "rot"}) + timestamp: OptionalDuration = field(metadata={"data_field": "ts"}) + + +@dataclass(kw_only=True) +class PointerEvent(Event[EventControlType]): + kind: PointerDeviceType = field(metadata={"data_field": "k"}) + local_x: float = field(metadata={"data_field": "lx"}) + local_y: float = field(metadata={"data_field": "ly"}) + global_x: float = field(metadata={"data_field": "gx"}) + global_y: float = field(metadata={"data_field": "gy"}) + timestamp: Duration = field(metadata={"data_field": "ts"}) + view_id: int = field(metadata={"data_field": "vId"}) + buttons: float = field(metadata={"data_field": "btt"}) + obscured: bool = field(metadata={"data_field": "obs"}) + device: float = field(metadata={"data_field": "dev"}) + pressure: float = field(metadata={"data_field": "ps"}) + pressure_min: float = field(metadata={"data_field": "pMin"}) + pressure_max: float = field(metadata={"data_field": "pMax"}) + distance: float = field(metadata={"data_field": "dist"}) + distance_max: float = field(metadata={"data_field": "distMax"}) + size: float = field(metadata={"data_field": "size"}) + radius_major: float = field(metadata={"data_field": "rMj"}) + radius_minor: float = field(metadata={"data_field": "rMn"}) + radius_min: float = field(metadata={"data_field": "rMin"}) + radius_max: float = field(metadata={"data_field": "rMax"}) + orientation: float = field(metadata={"data_field": "or"}) + tilt: float = field(metadata={"data_field": "tilt"}) + embedder_id: float = field(metadata={"data_field": "eId"}) + delta_x: Optional[float] = field(metadata={"data_field": "dx"}) + delta_y: Optional[float] = field(metadata={"data_field": "dy"}) + + +@dataclass(kw_only=True) +class ScrollEvent(Event[EventControlType]): + local_x: float = field(metadata={"data_field": "lx"}) + local_y: float = field(metadata={"data_field": "ly"}) + global_x: float = field(metadata={"data_field": "gx"}) + global_y: float = field(metadata={"data_field": "gy"}) + scroll_delta_x: float = field(metadata={"data_field": "sdx"}) + scroll_delta_y: float = field(metadata={"data_field": "sdy"}) + + +HoverEvent = PointerEvent diff --git a/sdk/python/packages/flet/src/flet/core/exceptions.py b/sdk/python/packages/flet/src/flet/controls/exceptions.py similarity index 100% rename from sdk/python/packages/flet/src/flet/core/exceptions.py rename to sdk/python/packages/flet/src/flet/controls/exceptions.py diff --git a/sdk/python/packages/flet/src/flet/controls/geometry.py b/sdk/python/packages/flet/src/flet/controls/geometry.py new file mode 100644 index 000000000..1da094c39 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/geometry.py @@ -0,0 +1,142 @@ +from dataclasses import dataclass + +from flet.controls.transform import Offset +from flet.controls.types import Number + +__all__ = [ + "Size", + "Rect", +] + + +@dataclass +class Size: + """ + A 2D size with width and height. + """ + + width: Number + height: Number + + @property + def aspect_ratio(self) -> float: + """Returns the aspect ratio (width / height).""" + if self.height != 0.0: + return self.width / self.height + if self.width > 0.0: + return float("inf") + if self.width < 0.0: + return float("-inf") + return 0.0 + + def is_infinite(self) -> bool: + """Checks if either dimension is infinite.""" + return self.width == float("inf") or self.height == float("inf") + + def is_finite(self) -> bool: + """Checks if both dimensions are finite.""" + return self.width != float("inf") and self.height != float("inf") + + @classmethod + def copy(cls, source: "Size") -> "Size": + """Creates a copy of the given Size object.""" + return Size(source.width, source.height) + + @classmethod + def square(cls, dimension: Number) -> "Size": + """Creates a square Size where width and height are the same.""" + return Size(dimension, dimension) + + @classmethod + def from_width(cls, width: Number) -> "Size": + """Creates a Size with the given width and an infinite height.""" + return Size(width, float("inf")) + + @classmethod + def from_height(cls, height: Number) -> "Size": + """Creates a Size with the given height and an infinite width.""" + return Size(float("inf"), height) + + @classmethod + def from_radius(cls, radius: Number) -> "Size": + """Creates a square Size whose width and height are twice the given radius.""" + return Size(radius * 2.0, radius * 2.0) + + @classmethod + def zero(cls): + return Size(0.0, 0.0) + + @classmethod + def infinite(cls): + return Size(float("inf"), float("inf")) + + +@dataclass +class Rect: + """ + A 2D, axis-aligned, floating-point rectangle whose coordinates are relative to a given origin. + """ + + left: Number + """The offset of the left edge of this rectangle from the x-axis.""" + + top: Number + """The offset of the top edge of this rectangle from the y-axis.""" + + right: Number + """The offset of the right edge of this rectangle from the x-axis.""" + + bottom: Number + """The offset of the bottom edge of this rectangle from the y-axis.""" + + @property + def width(self) -> Number: + """The distance between the left and right edges of this rectangle.""" + return self.right - self.left + + @property + def height(self) -> Number: + """The distance between the top and bottom edges of this rectangle.""" + return self.bottom - self.top + + @property + def size(self) -> Size: + """ + The distance between the upper-left corner + and the lower-right corner of this rectangle. + """ + return Size(self.width, self.height) + + @classmethod + def from_lwth(cls, *, left: Number, top: Number, width: Number, height: Number) -> "Rect": + """ + Construct a rectangle from its left and top edges, + its width, and its height. + """ + return Rect(left, top, left + width, top + height) + + @classmethod + def from_center(cls, *, center: Offset, width: Number, height: Number): + """ + Constructs a rectangle from its center point, width, and height. + The `center` argument is assumed to be an offset from the origin. + """ + return Rect( + center.x - width / 2, + center.y - height / 2, + center.x + width / 2, + center.y + height / 2, + ) + + @classmethod + def from_points(cls, a: Offset, b: Offset): + """ + Construct the smallest rectangle that encloses the given offsets, + treating them as vectors from the origin. + """ + return Rect( + min(a.x, b.x), + min(a.y, b.y), + max(a.x, b.x), + max(a.y, b.y), + ) \ No newline at end of file diff --git a/sdk/python/packages/flet/src/flet/controls/gradients.py b/sdk/python/packages/flet/src/flet/controls/gradients.py new file mode 100644 index 000000000..6f02967ce --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/gradients.py @@ -0,0 +1,193 @@ +import math +from dataclasses import dataclass, field +from enum import Enum +from typing import Optional + +from flet.controls.alignment import Alignment + +__all__ = [ + "Gradient", + "LinearGradient", + "RadialGradient", + "SweepGradient", + "GradientTileMode", +] + +from flet.controls.types import Number, OptionalNumber + + +class GradientTileMode(Enum): + """ + Defines what happens at the edge of a gradient. + More information about GradientTileMode [here](https://api.flutter.dev/flutter/dart-ui/TileMode.html). + """ + + CLAMP = "clamp" + """ + Samples beyond the edge are clamped to the nearest color in the defined inner area. + More information on CLAMP GradientTileMode [here](https://api.flutter.dev/flutter/dart-ui/TileMode.html#clamp). + """ + + DECAL = "decal" + """ + Samples beyond the edge are treated as transparent black. + More information on DECAL GradientTileMode [here](https://api.flutter.dev/flutter/dart-ui/TileMode.html#decal). + """ + + MIRROR = "mirror" + """ + Samples beyond the edge are mirrored back and forth across the defined area. + More information on MIRROR GradientTileMode [here](https://api.flutter.dev/flutter/dart-ui/TileMode.html#mirror). + """ + + REPEATED = "repeated" + """ + Samples beyond the edge are repeated from the far end of the defined area. + More information on REPEATED GradientTileMode [here](https://api.flutter.dev/flutter/dart-ui/TileMode.html#repeated). + """ + + +@dataclass +class Gradient: + """ + A shader that renders a color gradient. + + There are several types of gradients: `LinearGradient`, `RadialGradient` and + `SweepGradient`. + """ + + colors: list[str] + """ + The [colors](https://flet.dev/docs/reference/colors) the gradient should obtain at + each of the stops. This list must contain at least two colors. + + If `stops` is provided, this list must have the same length as `stops`. + """ + + tile_mode: GradientTileMode = GradientTileMode.CLAMP + """ + How this gradient should tile the plane beyond in the region before `begin` and + after `end`. The value is of type + [GradientTileMode](https://flet.dev/docs/reference/types/gradienttilemode). + """ + + rotation: OptionalNumber = None + """ + The rotation of the gradient in + [radians](https://en.wikipedia.org/wiki/Radian), around the center-point of its + bounding box. + """ + + stops: Optional[list[Number]] = None + """ + A list of values from `0.0` to `1.0` that denote fractions along the gradient. + + If provided, this list must have the same length as `colors`. If the first value is + not `0.0`, then a stop with position `0.0` and a color equal to the first color in + `colors` is implied. If the last value is not `1.0`, then a stop with position `1.0` + and a color equal to the last color in `colors` is implied. + """ + + _type: Optional[str] = field(init=False, repr=False, compare=False, default=None) + + +@dataclass +class LinearGradient(Gradient): + """ + Creates a linear gradient from `begin` to `end`. + + More information on Linear gradient [here](https://api.flutter.dev/flutter/painting/LinearGradient-class.html). + """ + + begin: Alignment = field(default_factory=lambda: Alignment.center_left()) + """ + An instance of [Alignment](https://flet.dev/docs/reference/types/alignment). The + offset at which stop `0.0` of the gradient is placed. + """ + + end: Alignment = field(default_factory=lambda: Alignment.center_right()) + """ + An instance of [Alignment](https://flet.dev/docs/reference/types/alignment). The + offset at which stop `1.0` of the gradient is placed. + """ + + def __post_init__(self): + self._type = "linear" + + +@dataclass +class RadialGradient(Gradient): + """ + Creates a radial gradient centered at center that ends at radius distance from the + center. + + More information on Radial gradient [here](https://api.flutter.dev/flutter/painting/ + RadialGradient-class.html)). + """ + + center: Alignment = field(default_factory=lambda: Alignment.center()) + """ + An instance of [Alignment](https://flet.dev/docs/reference/types/alignment) class. + The center of the gradient, as an offset into the (-1.0, -1.0) x (1.0, 1.0) square + describing the gradient which will be mapped onto the paint box. For example, an + alignment of (0.0, 0.0) will place the radial gradient in the center of the box. + """ + + radius: Number = 0.5 + """ + The radius of the gradient, as a fraction of the shortest side of the paint box. + For example, if a radial gradient is painted on a box that is 100.0 pixels wide and + 200.0 pixels tall, then a radius of 1.0 will place the 1.0 stop at 100.0 pixels from + the `center`. + """ + + focal: Optional[Alignment] = None + """ + The focal point of the gradient. If specified, the gradient will appear to be + focused along the vector from `center` to focal. + """ + + focal_radius: Number = 0.0 + """ + The radius of the focal point of gradient, as a fraction of the shortest side of the + paint box. For example, if a radial gradient is painted on a box that is 100.0 + pixels wide and 200.0 pixels tall, then a radius of 1.0 will place the 1.0 stop at + 100.0 pixels from the focal point. + """ + + def __post_init__(self): + self._type = "radial" + + +@dataclass +class SweepGradient(Gradient): + """ + Creates a sweep gradient centered at center that starts at `start_angle` and ends + at `end_angle`. + + More information on Sweep gradient [here](https://api.flutter.dev/flutter/painting/ + SweepGradient-class.html). + """ + + center: Alignment = field(default_factory=lambda: Alignment.center()) + """ + The center of the gradient, as an offset into the (-1.0, -1.0) x (1.0, 1.0) square + describing the gradient which will be mapped onto the paint box. For example, an + alignment of (0.0, 0.0) will place the sweep gradient in the center of the box. + """ + + start_angle: Number = 0.0 + """ + The angle in [radians](https://en.wikipedia.org/wiki/Radian) at which stop 0.0 of + the gradient is placed. Defaults to 0.0. + """ + + end_angle: Number = math.pi * 2 + """ + The angle in [radians](https://en.wikipedia.org/wiki/Radian) at which stop 1.0 of + the gradient is placed. Defaults to math.pi * 2. + """ + + def __post_init__(self): + self._type = "sweep" + diff --git a/sdk/python/packages/flet/src/flet/controls/keys.py b/sdk/python/packages/flet/src/flet/controls/keys.py new file mode 100644 index 000000000..ae42b39f5 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/keys.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass, field +from typing import Optional, Union + + +@dataclass() +class Key: + value: Union[str, int, float, bool] + _type: Optional[str] = field(init=False, repr=False, compare=False, default=None) + + def __str__(self) -> str: + return str(self.value) + + +@dataclass +class ValueKey(Key): + def __post_init__(self): + self._type = "value" + + +@dataclass +class ScrollKey(Key): + def __post_init__(self): + self._type = "scroll" diff --git a/sdk/python/packages/flet/src/flet/controls/margin.py b/sdk/python/packages/flet/src/flet/controls/margin.py new file mode 100644 index 000000000..13667a6b0 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/margin.py @@ -0,0 +1,94 @@ +from dataclasses import dataclass +from typing import Optional, Union + +from flet.controls.types import Number +from flet.utils import deprecated + +__all__ = ["Margin", "MarginValue", "OptionalMarginValue", "all", "symmetric", "only"] + + +@dataclass +class Margin: + """ + `Margin` class has the properties to set margins for all sides of the rectangle. + """ + + left: Number = 0 + """ + The margin applied to the left. + """ + + top: Number = 0 + """ + The margin applied to the top. + """ + + right: Number = 0 + """ + The margin applied to the right. + """ + + bottom: Number = 0 + """ + The margin applied to the bottom. + """ + + @classmethod + def all(cls, value: Number) -> "Margin": + """ + Applies the same margin to all sides. + """ + return Margin(left=value, top=value, right=value, bottom=value) + + @classmethod + def symmetric(cls, *, vertical: Number = 0, horizontal: Number = 0) -> "Margin": + """ + Applies `vertical` margin to top and bottom sides and `horizontal` margin to + left and right sides. + """ + return Margin(left=horizontal, top=vertical, right=horizontal, bottom=vertical) + + @classmethod + def only( + cls, *, left: Number = 0, top: Number = 0, right: Number = 0, bottom: Number = 0 + ) -> "Margin": + """ + Applies margin to the specified sides. + """ + return Margin(left=left, top=top, right=right, bottom=bottom) + + +@deprecated( + reason="Use Margin.all() instead", + version="0.70.0", + delete_version="0.73.0", + show_parentheses=True, +) +def all(value: float) -> Margin: + return Margin(left=value, top=value, right=value, bottom=value) + + +@deprecated( + reason="Use Margin.symmetric() instead", + version="0.70.0", + delete_version="0.73.0", + show_parentheses=True, +) +def symmetric(vertical: float = 0, horizontal: float = 0) -> Margin: + return Margin(left=horizontal, top=vertical, right=horizontal, bottom=vertical) + + +@deprecated( + reason="Use Margin.only() instead", + version="0.70.0", + delete_version="0.73.0", + show_parentheses=True, +) +def only( + left: float = 0, top: float = 0, right: float = 0, bottom: float = 0 +) -> Margin: + return Margin(left=left, top=top, right=right, bottom=bottom) + + +MarginValue = Union[Number, Margin] +OptionalMarginValue = Optional[MarginValue] diff --git a/sdk/python/packages/flet/src/flet/controls/material/__init__.py b/sdk/python/packages/flet/src/flet/controls/material/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sdk/python/packages/flet/src/flet/controls/material/alert_dialog.py b/sdk/python/packages/flet/src/flet/controls/material/alert_dialog.py new file mode 100644 index 000000000..1120133f3 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/alert_dialog.py @@ -0,0 +1,237 @@ +from dataclasses import field +from typing import Optional + +from flet.controls.alignment import Alignment +from flet.controls.base_control import control +from flet.controls.buttons import OutlinedBorder +from flet.controls.control import Control +from flet.controls.dialog_control import DialogControl +from flet.controls.padding import OptionalPaddingValue +from flet.controls.text_style import TextStyle +from flet.controls.types import ( + ClipBehavior, + MainAxisAlignment, + OptionalColorValue, + OptionalNumber, + StrOrControl, +) + +__all__ = ["AlertDialog"] + + +@control("AlertDialog") +class AlertDialog(DialogControl): + """ + An alert dialog informs the user about situations that require acknowledgement. An + alert dialog has an optional title and an optional list of actions. The title is + displayed above the content and the actions are displayed below the content. + + Online docs: https://flet.dev/docs/controls/alertdialog + """ + + content: Optional[Control] = None + """ + The (optional) content of the dialog is displayed in the center of the dialog in a + lighter font. Typically this is a [`Column`](https://flet.dev/docs/controls/column) + that contains the dialog's [`Text`](https://flet.dev/docs/controls/text) message. + + Value is of type `Control`. + """ + + modal: bool = False + """ + Whether dialog can be dismissed/closed by clicking the area outside of it. + + Value is of type `bool` and defaults to `False`. + """ + + title: Optional[StrOrControl] = None + """ + The (optional) title of the dialog is displayed in a large font at the top of the + dialog. + + Typically a [`Text`](https://flet.dev/docs/controls/text) control. + """ + + actions: list[Control] = field(default_factory=list) + """ + The (optional) set of actions that are displayed at the bottom of the dialog. + + Typically this is a list of [`TextButton`](https://flet.dev/docs/controls/textbutton) + controls. + """ + + bgcolor: OptionalColorValue = None + """ + The background [color](https://flet.dev/docs/reference/colors) of the dialog's + surface. + """ + + elevation: OptionalNumber = None + """ + Defines the elevation (z-coordinate) at which the dialog should appear. + + Value is of type [`OptionalNumber`](https://flet.dev/docs/reference/types/aliases#optionalnumber). + """ + + icon: Optional[Control] = None + """ + A control that is displayed at the top of the dialog. Typically a [`Icon`](https://flet.dev/docs/controls/icon) + control. + """ + + title_padding: OptionalPaddingValue = None + """ + Padding around the title. + + If there is no title, no padding will be provided. Otherwise, this padding is used. + + Value is of type [`PaddingValue`](https://flet.dev/docs/reference/types/aliases#paddingvalue). + + Defaults to providing `24` pixels on the top, left, and right of the title. If the + `content` is not `None`, then no bottom padding is provided (but see [`content_padding`](https://flet.dev/docs/reference/types/aliases#paddingvalue)). + If it is not set, then an extra `20` pixels of bottom padding is added to separate + the title from the actions. + """ + + content_padding: OptionalPaddingValue = None + """ + Padding around the content. + + If there is no content, no padding will be provided. Otherwise, padding of 20 + pixels is provided above the content to separate the content from the title, and + padding of 24 pixels is provided on the left, right, and bottom to separate the + content from the other edges of the dialog. + + Value is of type [`PaddingValue`](https://flet.dev/docs/reference/types/aliases#paddingvalue). + """ + + actions_padding: OptionalPaddingValue = None + """ + Padding around the set of actions at the bottom of the dialog. + + Typically used to provide padding to the button bar between the button bar and the + edges of the dialog. + + If are no actions, then no padding will be included. The padding around the button + bar defaults to zero. + + Value is of type [`PaddingValue`](https://flet.dev/docs/reference/types/aliases#paddingvalue). + """ + + actions_alignment: Optional[MainAxisAlignment] = None + """ + Defines the horizontal layout of the actions. + + Value is of type [`MainAxisAlignment`](https://flet.dev/docs/reference/types/mainaxisalignment) + and defaults to `MainAxisAlignment.END`. + """ + + shape: Optional[OutlinedBorder] = None + """ + The shape of the dialog. + + Value is of type [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder) + and defaults to `RoundedRectangleBorder(radius=4.0)`. + """ + + inset_padding: OptionalPaddingValue = None + """ + Padding around the Dialog itself. + + Value is of type [`PaddingValue`](https://flet.dev/docs/reference/types/aliases#paddingvalue). + + Defaults to `padding.symmetric(vertical=40, horizontal=24)` - 40 pixels + horizontally and 24 pixels vertically outside of the dialog box. + """ + + icon_padding: OptionalPaddingValue = None + """ + Padding around the `icon`. + + Value is of type [`PaddingValue`](https://flet.dev/docs/reference/types/aliases#paddingvalue). + """ + + action_button_padding: OptionalPaddingValue = None + """ + The padding that surrounds each button in `actions`. + + Value is of type [`PaddingValue`](https://flet.dev/docs/reference/types/aliases#paddingvalue). + """ + + surface_tint_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used as a surface tint overlay + on the dialog's background color, which reflects the dialog's elevation. + """ + + shadow_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used to paint a drop shadow + under the dialog, which reflects the dialog's elevation. + """ + + icon_color: OptionalColorValue = None + """ + TBD + """ + + scrollable: bool = False + """ + TBD + """ + + actions_overflow_button_spacing: OptionalNumber = None + """ + TBD + """ + + alignment: Optional[Alignment] = None + """ + TBD + """ + + content_text_style: Optional[TextStyle] = None + """ + TBD + """ + + title_text_style: Optional[TextStyle] = None + """ + TBD + """ + + clip_behavior: Optional[ClipBehavior] = None + """ + Controls how the contents of the dialog are clipped (or not) to the given `shape`. + + Value is of type [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) + and defaults to `ClipBehavior.NONE`. + """ + + semantics_label: Optional[str] = None + """ + The semantic label of the dialog used by accessibility frameworks to announce + screen transitions when the dialog is opened and closed. + + In iOS, if this label is not provided, a semantic label will be inferred from the + `title` if it is not null. + + Value is of type `str`. + """ + + barrier_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the modal barrier that + darkens everything below the dialog. + + If `None`, the [`DialogTheme.barrier_color`](https://flet.dev/docs/reference/types/dialogtheme#barrier_color) + is used. If it is also `None`, then `Colors.BLACK_54` is used. + """ + + def before_update(self): + super().before_update() + assert ( + self.title or self.content or self.actions + ), "AlertDialog has nothing to display. Provide at minimum one of the " + "following: title, content, actions" 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 new file mode 100644 index 000000000..98a3d100e --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/app_bar.py @@ -0,0 +1,219 @@ +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.buttons import OutlinedBorder +from flet.controls.control import Control +from flet.controls.text_style import TextStyle +from flet.controls.types import ( + ClipBehavior, + Number, + OptionalColorValue, + OptionalNumber, + StrOrControl, +) + + +@control("AppBar") +class AppBar(AdaptiveControl): + """ + A material design app bar. + + Online docs: https://flet.dev/docs/controls/appbar + """ + + leading: Optional[Control] = None + """ + A `Control` to display before the toolbar's title. + + Typically the leading control is an [`Icon`](https://flet.dev/docs/controls/icon) + or an [`IconButton`](https://flet.dev/docs/controls/iconbutton). + + Value is of type `Control`. + """ + + leading_width: OptionalNumber = None + """ + Defines the width of leading control. + + Value is of type [`OptionalNumber`](https://flet.dev/docs/reference/types/aliases#optionalnumber) + and defaults to `56.0`. + """ + + automatically_imply_leading: bool = True + """ + Controls whether we should try to imply the leading widget if null. + + If `True` and `leading` is null, automatically try to deduce what the leading + widget should be. If `False` and `leading` is null, leading space is given to + title. If leading widget is not null, this parameter has no effect. + + Value is of type `bool`. + """ + + title: Optional[StrOrControl] = None + """ + The primary `Control` displayed in the app bar. Typically a [`Text`](https://flet.dev/docs/controls/text) + control that contains a description of the current contents of the app. + + **Note** that, if `AppBar.adaptive=True` and the app is opened on an iOS or macOS + device, this control will be automatically centered. + + Value is of type `Control`. + """ + + center_title: bool = False + """ + Whether the title should be centered. + + Value is of type `bool` and defaults to `False`. + """ + + toolbar_height: OptionalNumber = None + """ + Defines the height of the toolbar component of an AppBar. + + Value is of type [`OptionalNumber`](https://flet.dev/docs/reference/types/aliases#optionalnumber) + and defaults to `56.0`. + """ + + color: OptionalColorValue = None + """ + The default [color](https://flet.dev/docs/reference/colors) for `Text` and `Icon` + controls within the app bar. Default color is defined by current theme. + """ + + bgcolor: OptionalColorValue = None + """ + The fill [color](https://flet.dev/docs/reference/colors) to use for an AppBar. + Default color is defined by current theme. + """ + + elevation: OptionalNumber = None + """ + The app bar's elevation. + + Note: This effect is only visible when using the Material 2 design + (`Theme.use_material3=False`). + + Value is of type [`OptionalNumber`](https://flet.dev/docs/reference/types/aliases#optionalnumber) + and defaults to `4`. + """ + + elevation_on_scroll: OptionalNumber = None + """ + The elevation to be used if this app bar has something scrolled underneath it. + + Value is of type [`OptionalNumber`](https://flet.dev/docs/reference/types/aliases#optionalnumber). + """ + + shadow_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the shadow below the app bar. + + A shadow is only visible and displayed if the `elevation` is greater than zero. + """ + + surface_tint_color: OptionalColorValue = None + """ + The color of the surface tint overlay applied to the app bar's `bgcolor` to + indicate elevation. + + By default, no overlay will be applied. + """ + + clip_behavior: Optional[ClipBehavior] = None + """ + The content will be clipped (or not) according to this option. + + Value is of type [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior). + """ + + force_material_transparency: bool = False + """ + Forces the app bar to be transparent (instead of Material's default type). + + This will also remove the visual display of `bgcolor` and `elevation`, and affect + other characteristics of this app bar. + + Value is of type `bool`. + """ + + is_secondary: bool = False + """ + Whether this app bar is not being displayed at the top of the screen. + + Value is of type `bool` and defaults to `False`. + """ + + title_spacing: OptionalNumber = None + """ + The spacing around `title` on the horizontal axis. It is applied even if there are + no `leading` or `actions` controls. + + If you want `title` to take all the space available, set this value to `0.0`. + + Value is of type [`OptionalNumber`](https://flet.dev/docs/reference/types/aliases#optionalnumber). + """ + + exclude_header_semantics: bool = False + """ + Whether the `title` should be wrapped with header [`Semantics`](https://flet.dev/docs/controls/semantics). + + Value is of type `bool` and defaults to `False`. + """ + + actions: Optional[list[Control]] = None + """ + A list of `Control`s to display in a row after the title control. + + Typically these controls are [`IconButtons`](https://flet.dev/docs/controls/iconbutton) + representing common operations. For less common operations, consider using a + [`PopupMenuButton`](https://flet.dev/docs/controls/popupmenubutton) as the last + action. + + **Note** that, if `AppBar.adaptive=True` and the app is opened on an iOS or macOS + device, only the first element of this list will be used. This is because the + `CupertinoAppBar`(which will be used on those two platforms) only accepts one - + trailing - action control. + """ + + toolbar_opacity: Number = 1.0 + """ + The opacity of the toolbar. Value ranges from `0.0` (transparent) to `1.0` (fully + opaque). + + Value is of type [`OptionalNumber`](https://flet.dev/docs/reference/types/aliases#optionalnumber) + and defaults to `1.0`. + """ + + title_text_style: Optional[TextStyle] = None + """ + The style to be used for the `Text` controls in the `title`. + + Value is of type [`TextStyle`](https://flet.dev/docs/reference/types/textstyle). + """ + + toolbar_text_style: Optional[TextStyle] = None + """ + The style to be used for the `Text` controls in the app bar's `leading` and + `actions` (but not `title`). + + Value is of type [`TextStyle`](https://flet.dev/docs/reference/types/textstyle). + """ + + shape: Optional[OutlinedBorder] = None + """ + The shape of the app bar's Material as well as its shadow. + + Value is of type [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder). + """ + + def before_update(self): + super().before_update() + assert ( + self.elevation is None or self.elevation >= 0 + ), "elevation cannot be negative" + assert ( + self.elevation_on_scroll is None or self.elevation_on_scroll >= 0 + ), "elevation_on_scroll cannot be negative" diff --git a/sdk/python/packages/flet/src/flet/controls/material/auto_complete.py b/sdk/python/packages/flet/src/flet/controls/material/auto_complete.py new file mode 100644 index 000000000..97e1124e3 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/auto_complete.py @@ -0,0 +1,63 @@ +from dataclasses import dataclass, field + +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.control_event import Event, OptionalEventHandler +from flet.controls.types import Number + +__all__ = ["AutoComplete", "AutoCompleteSuggestion", "AutoCompleteSelectEvent"] + + +@dataclass +class AutoCompleteSuggestion: + key: str + value: str + + +@dataclass +class AutoCompleteSelectEvent(Event["AutoComplete"]): + selection: AutoCompleteSuggestion + + +@control("AutoComplete") +class AutoComplete(Control): + """ + Helps the user make a selection by entering some text and choosing from among a + list of displayed options. + + Online docs: https://flet.dev/docs/controls/autocomplete + """ + + suggestions: list[AutoCompleteSuggestion] = field(default_factory=list) + """ + A list of [`AutoCompleteSuggestion`](https://flet.dev/docs/reference/types/autocompletesuggestion) + controls representing the suggestions to be displayed. + + **Note:** + + - The internal filtration process of the suggestions (based on their `key`s) with + respect to the user's input is case-insensitive because the comparison is done + in lowercase. + - A valid `AutoCompleteSuggestion` must have at least a `key` or `value` specified, + else it will be ignored. If only `key` is provided, `value` will be set to `key` + as fallback and vice versa. + """ + + suggestions_max_height: Number = 200 + """ + The maximum - visual - height of the suggestions list. + + Value is of type [`OptionalNumber`](https://flet.dev/docs/reference/types/aliases#optionalnumber) + and defaults to `200`. + """ + + on_select: OptionalEventHandler[AutoCompleteSelectEvent] = None + """ + Fires when a suggestion is selected. + + Event handler is of type [`AutoCompleteSelectEvent`](https://flet.dev/docs/reference/types/autocompleteselectevent). + """ + + @property + def selected_index(self): + return self._selected_index diff --git a/sdk/python/packages/flet/src/flet/controls/material/badge.py b/sdk/python/packages/flet/src/flet/controls/material/badge.py new file mode 100644 index 000000000..22b9e1560 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/badge.py @@ -0,0 +1,123 @@ +from typing import Optional, Union + +from flet.controls.alignment import Alignment +from flet.controls.base_control import BaseControl, control +from flet.controls.padding import OptionalPaddingValue +from flet.controls.text_style import TextStyle +from flet.controls.transform import OffsetValue +from flet.controls.types import OptionalColorValue, OptionalNumber, StrOrControl + +__all__ = ["Badge", "BadgeValue"] + + +@control("Badge") +class Badge(BaseControl): + """ + Badges are used to show notifications, counts, or status information on navigation + items such as NavigationBar or NavigationRail destinations + or a button's icon. + + Online docs: https://flet.dev/docs/reference/types/badge + """ + + label: Optional[StrOrControl] = None + """ + + The text or Control shown on badge's label, typically a 1 to 4 characters text. + + If the label is not provided, the badge is shown as a filled circle of + [`small_size`](#small_size) diameter. + + If `label` is provided, the label is a StadiumBorder shaped badge with height equal + to [`large_size`](#large_size). + + Value is of type `str` or `Control`. + """ + + offset: Optional[OffsetValue] = None + """ + Combined with `alignment` to determine the location of the label relative to the + content. + + Has effect only used if `label` is also provided. + + Value is of type [`OffsetValue`](https://flet.dev/docs/reference/types/aliases#offsetvalue). + """ + + alignment: Optional[Alignment] = None + """ + Aligns the label relative to the content of the badge. + + The alignment positions the label in similar way content of a container is + positioned using its [`alignment`](https://flet.dev/docs/controls/container#alignment), + except that the badge alignment is resolved as if the label was a [`large_size`](https://flet.dev/docs/reference/types/badge#large_size) + square and `offset` is added to the result. + + This value is only used if `label` property is provided. + + For example: + + ```python + badge.alignment = ft.Alignment.top_left() + ``` + + Value is of type [`Alignment`](https://flet.dev/docs/reference/types/alignment). + """ + + bgcolor: OptionalColorValue = None + """ + Background [color](https://flet.dev/docs/reference/colors) of the label. + """ + + label_visible: bool = True + """ + If `False`, the `label` is not displayed. By default, `label_visible` is True. It + can be used to create a badge only shown under certain conditions. + + Value is of type `bool`. + """ + + large_size: OptionalNumber = None + """ + The badge's label height if `label` is provided. + + If the default value is overridden then it may be useful to also override `padding` + and `alignment`. + + Value is of type [`OptionalNumber`](https://flet.dev/docs/reference/types/aliases#optionalnumber) + and defaults to `16`. + """ + + padding: OptionalPaddingValue = None + """ + The padding added to the badge's label. + + This value is only used if `text` is provided. Defaults to 4 pixels on the left and + right. + + Value is of type [`PaddingValue`](https://flet.dev/docs/reference/types/aliases#paddingvalue). + """ + + small_size: OptionalNumber = None + """ + The badge's label diameter if `label` is not provided. + + Value is of type [`OptionalNumber`](https://flet.dev/docs/reference/types/aliases#optionalnumber) + and defaults to `6`. + """ + + text_color: OptionalColorValue = None + """ + [Color](https://flet.dev/docs/reference/colors) of the text shown in the label. + This color overrides the color of the label's `text_style`. + """ + + text_style: Optional[TextStyle] = None + """ + The text style to use for text in the label. + + Value is of type [`TextStyle`](https://flet.dev/docs/reference/types/textstyle). + """ + + +BadgeValue = Union[str, Badge] diff --git a/sdk/python/packages/flet/src/flet/controls/material/banner.py b/sdk/python/packages/flet/src/flet/controls/material/banner.py new file mode 100644 index 000000000..e09fc2837 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/banner.py @@ -0,0 +1,154 @@ +from dataclasses import field +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.dialog_control import DialogControl +from flet.controls.margin import OptionalMarginValue +from flet.controls.padding import OptionalPaddingValue +from flet.controls.text_style import TextStyle +from flet.controls.types import ( + IconValueOrControl, + Number, + OptionalColorValue, + OptionalNumber, + StrOrControl, +) + +__all__ = ["Banner"] + + +@control("Banner") +class Banner(DialogControl): + """ + A banner displays an important, succinct message, and provides actions for users to + address (or dismiss the banner). A user action is required for it to be dismissed. + + Banners are displayed at the top of the screen, below a top app bar. They are + persistent and non-modal, allowing the user to either ignore them or interact with + them at any time. + + Online docs: https://flet.dev/docs/controls/banner + """ + + content: StrOrControl + """ + The content of the Banner. + + Typically a [`Text`](https://flet.dev/docs/controls/text) control. + """ + + actions: list[Control] = field(default_factory=list) + """ + The set of actions that are displayed at the bottom or trailing side of the Banner. + + Typically this is a list of [`TextButton`](https://flet.dev/docs/controls/textbutton) + controls. + """ + + leading: Optional[IconValueOrControl] = None + """ + The (optional) leading `Control` of the Banner. + + Typically an [`Icon`](https://flet.dev/docs/controls/icon) control. + """ + + leading_padding: OptionalPaddingValue = None + """ + The amount of space by which to inset the leading control. + + The value is an instance of [`padding.Padding`](https://flet.dev/docs/reference/types/padding) + class or a number. + + Defaults to `16` virtual pixels. + """ + + content_padding: OptionalPaddingValue = None + """ + The amount of space by which to inset the content. + + The value is an instance of [`padding.Padding`](https://flet.dev/docs/reference/types/padding) + class or a number. + + If the actions are below the content, this defaults to + `padding.only(left=16.0, top=24.0, right=16.0, bottom=4.0)`. + + If the actions are trailing the content, this defaults to + `padding.only(left=16.0, top=2.0)`. + """ + + force_actions_below: bool = False + """ + An override to force the actions to be below the content regardless of how many + there are. + + If this is `True`, the actions will be placed below the content. If this is + `False`, the actions will be placed on the trailing side of the content if + `actions` length is `1` and below the content if greater than `1`. + + Defaults to `False`. + """ + + bgcolor: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the surface of this Banner. + """ + + surface_tint_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used as an overlay on `bgcolor` + to indicate elevation. + """ + + shadow_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the shadow below the banner. + """ + + divider_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the divider. + """ + + elevation: OptionalNumber = None + """ + The elevation of the banner. + """ + + margin: OptionalMarginValue = None + """ + The amount of space surrounding the banner. + + The value is an instance of [`Margin`](https://flet.dev/docs/reference/types/margin) + class or a number. + """ + + content_text_style: Optional[TextStyle] = None + """ + The style to be used for the `Text` controls in the `content`. + + Value is of type [`TextStyle`](https://flet.dev/docs/reference/types/textstyle). + """ + + min_action_bar_height: Number = 52.0 + """ + The optional minimum action bar height. + + Defaults to `52`. + """ + + on_visible: OptionalControlEventHandler["Banner"] = None + """ + Fires when the banner is shown or made visible for the first time. + """ + + def before_update(self): + super().before_update() + assert self.elevation is None or self.elevation >= 0, ( + "elevation cannot be negative" + ) + assert self.content.visible, "content must be visible" + assert any(a.visible for a in self.actions), ( + "actions must contain at minimum one visible action Control" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/material/bottom_app_bar.py b/sdk/python/packages/flet/src/flet/controls/material/bottom_app_bar.py new file mode 100644 index 000000000..4496c10ce --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/bottom_app_bar.py @@ -0,0 +1,94 @@ +from numbers import Number +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.padding import OptionalPaddingValue +from flet.controls.types import ( + ClipBehavior, + NotchShape, + OptionalColorValue, + OptionalNumber, +) + +__all__ = ["BottomAppBar"] + + +@control("BottomAppBar") +class BottomAppBar(ConstrainedControl): + """ + A material design bottom app bar. + + Online docs: https://flet.dev/docs/controls/bottomappbar + """ + + content: Optional[Control] = None + """ + A child Control contained by the BottomAppBar. + """ + + surface_tint_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used as an overlay on `bgcolor` + to indicate elevation. + + If this is `None`, no overlay will be applied. Otherwise this color will be + composited on top of `bgcolor` with an opacity related to `elevation` and used to + paint the BottomAppBar's background. + + Defaults to `None`. + """ + + bgcolor: OptionalColorValue = None + """ + The fill [color](https://flet.dev/docs/reference/colors) to use for the + BottomAppBar. Default color is defined by current theme. + """ + + shadow_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the shadow below the + BottomAppBar. + """ + + padding: OptionalPaddingValue = None + """ + Empty space to inscribe inside a container decoration (background, border). Padding + is an instance of [`Padding`](https://flet.dev/docs/reference/types/padding) class. + + Defaults to `padding.symmetric(vertical=12.0, horizontal=16.0)`. + """ + + clip_behavior: ClipBehavior = ClipBehavior.NONE + """ + The content will be clipped (or not) according to this option. + + Value is of type [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) + and defaults to `ClipBehavior.NONE`. + """ + + shape: Optional[NotchShape] = None + """ + The notch that is made for the floating action button. + + Value is of type [`NotchShape`](https://flet.dev/docs/reference/types/notchshape). + """ + + notch_margin: Number = 4.0 + """ + The margin between the `FloatingActionButton` and the BottomAppBar's notch. + + Can be visible only if `shape=None`. + """ + + elevation: OptionalNumber = None + """ + This property controls the size of the shadow below the BottomAppBar. + """ + + def before_update(self): + super().before_update() + assert ( + self.elevation is None or self.elevation >= 0 + ), "elevation cannot be negative" diff --git a/sdk/python/packages/flet/src/flet/controls/material/bottom_sheet.py b/sdk/python/packages/flet/src/flet/controls/material/bottom_sheet.py new file mode 100644 index 000000000..d6da5e7f8 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/bottom_sheet.py @@ -0,0 +1,108 @@ +from typing import Optional + +from flet.controls.animation import AnimationStyle +from flet.controls.base_control import control +from flet.controls.box import BoxConstraints +from flet.controls.buttons import OutlinedBorder +from flet.controls.control import Control +from flet.controls.dialog_control import DialogControl +from flet.controls.types import ClipBehavior, OptionalColorValue, OptionalNumber + + +@control("BottomSheet") +class BottomSheet(DialogControl): + """ + A modal bottom sheet is an alternative to a menu or a dialog and prevents the user + from interacting with the rest of the app. + + Online docs: https://flet.dev/docs/controls/bottomsheet + """ + + content: Control + """ + The content `Control` of the bottom sheet. + """ + + elevation: OptionalNumber = None + """ + Controls the size of the shadow below the BottomSheet. + """ + + bgcolor: OptionalColorValue = None + """ + The sheet's background [color](https://flet.dev/docs/reference/colors). + """ + + dismissible: bool = True + """ + Specifies whether the bottom sheet will be dismissed when user taps on the scrim. + """ + + enable_drag: bool = False + """ + Specifies whether the bottom sheet can be dragged up and down and dismissed by + swiping downwards. + """ + + show_drag_handle: bool = False + """ + Whether to display drag handle at the top of sheet or not. + """ + + use_safe_area: bool = True + """ + Specifies whether the sheet will avoid system intrusions on the top, left, and + right. + + Defaults to `False`. + """ + + is_scroll_controlled: bool = False + """ + Specifies if the bottom sheet contains scrollable content, such as ListView or + GridView. + + Defaults to `False`. + """ + + maintain_bottom_view_insets_padding: bool = True + """ + Adds a padding at the bottom to avoid obstructing bottom sheet content with + on-screen keyboard or other system elements. + """ + + animation_style: Optional[AnimationStyle] = None + """ + The sheet's animation style. + + Value is of type [`AnimationStyle`](https://flet.dev/docs/reference/types/animationstyle). + """ + + size_constraints: Optional[BoxConstraints] = None + """ + The size constraints to apply to the bottom sheet. + + Value is of type [`BoxConstraints`](https://flet.dev/docs/reference/types/boxconstraints). + """ + + clip_behavior: Optional[ClipBehavior] = None + """ + The sheet's clip behavior. + + Value is of type [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior). + """ + + shape: Optional[OutlinedBorder] = None + """ + Defines the shape of the bottom sheet. + + Value is of type [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder). + """ + + def before_update( + self, + ): + super().before_update() + assert ( + self.elevation is None or self.elevation >= 0 + ), "elevation cannot be negative" diff --git a/sdk/python/packages/flet/src/flet/controls/material/button.py b/sdk/python/packages/flet/src/flet/controls/material/button.py new file mode 100644 index 000000000..9e92f44ce --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/button.py @@ -0,0 +1,13 @@ +from flet.controls.material.elevated_button import ElevatedButton + +__all__ = ["Button"] + + +class Button(ElevatedButton): + """ + Elevated buttons or Buttons are essentially filled tonal buttons with a shadow. To + prevent shadow creep, only use them when absolutely necessary, such as when the + button requires visual separation from a patterned background. + + Online docs: https://flet.dev/docs/controls/elevatedbutton + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/card.py b/sdk/python/packages/flet/src/flet/controls/material/card.py new file mode 100644 index 000000000..04e34e2c7 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/card.py @@ -0,0 +1,107 @@ +from enum import Enum +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.buttons import OutlinedBorder +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.margin import OptionalMarginValue +from flet.controls.types import ClipBehavior, OptionalColorValue, OptionalNumber + +__all__ = ["Card", "CardVariant"] + + +class CardVariant(Enum): + ELEVATED = "elevated" + FILLED = "filled" + OUTLINED = "outlined" + + +@control("Card") +class Card(ConstrainedControl, AdaptiveControl): + """ + A material design card: a panel with slightly rounded corners and an elevation + shadow. + + Online docs: https://flet.dev/docs/controls/card + """ + + content: Optional[Control] = None + """ + The `Control` that should be displayed inside the card. + + This control can only have one child. To lay out multiple children, let this + control's child be a control such as [`Row`](https://flet.dev/docs/controls/row), + [`Column`](https://flet.dev/docs/controls/column), or [`Stack`](https://flet.dev/docs/controls/stack), + which have a children property, and then provide the children to that control. + """ + + margin: OptionalMarginValue = None + """ + The empty space that surrounds the card. + + Value can be one of the following types: `int`, `float`, or [`Margin`](https://flet.dev/docs/reference/types/margin). + """ + + elevation: OptionalNumber = None + """ + Controls the size of the shadow below the card. Default value is `1.0`. + """ + + color: OptionalColorValue = None + """ + The card's background [color](https://flet.dev/docs/reference/colors). + """ + + shadow_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to paint the shadow below the + card. + """ + + surface_tint_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used as an overlay on `color` + to indicate elevation. + + If this is `None`, no overlay will be applied. Otherwise this color will be + composited on top of `color` with an opacity related to `elevation` and used to + paint the background of the card. + """ + + shape: Optional[OutlinedBorder] = None + """ + The shape of the card. + + Value is of type [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder)`. + """ + + clip_behavior: Optional[ClipBehavior] = None + """ + The `content` will be clipped (or not) according to this option. + + """ + + is_semantic_container: bool = True + """ + Set to `True` (default) if this card represents a single semantic container, or to + `False` if it instead represents a collection of individual semantic nodes + (different types of content). + """ + + show_border_on_foreground: bool = True + """ + Whether the shape of the border should be painted in front of the `content` or + behind. + + Defaults to `True`. + """ + + variant: CardVariant = CardVariant.ELEVATED + """ + Defines the card variant to be used. + + Value is of type [`CardVariant`](https://flet.dev/docs/reference/types/cardvariant) + and defaults to `CardVariant.ELEVATED`. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/checkbox.py b/sdk/python/packages/flet/src/flet/controls/material/checkbox.py new file mode 100644 index 000000000..b7e27ec8c --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/checkbox.py @@ -0,0 +1,168 @@ +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.border import BorderSide +from flet.controls.buttons import OutlinedBorder +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.control_state import ControlStateValue +from flet.controls.text_style import TextStyle +from flet.controls.types import ( + ColorValue, + LabelPosition, + MouseCursor, + OptionalColorValue, + OptionalNumber, + StrOrControl, + VisualDensity, +) + +__all__ = ["Checkbox"] + + +@control("Checkbox") +class Checkbox(ConstrainedControl, AdaptiveControl): + """ + Checkbox allows to select one or more items from a group, or switch between two + mutually exclusive options (checked or unchecked, on or off). + + Online docs: https://flet.dev/docs/controls/checkbox + """ + + label: Optional[StrOrControl] = None + """ + The clickable label to display on the right of a checkbox. + """ + + value: Optional[bool] = None + """ + Current value of the checkbox. + """ + + label_position: LabelPosition = LabelPosition.RIGHT + """ + Defines on which side of the checkbox the `label` should be shown. + + Value is of type [`LabelPosition`](https://flet.dev/docs/reference/types/labelposition) + and defaults to `LabelPosition.RIGHT`. + """ + + label_style: Optional[TextStyle] = None + """ + The label's style. An instance of type [`TextStyle`](https://flet.dev/docs/reference/types/textstyle). + """ + + tristate: bool = False + """ + If `True` the checkboxes value can be `True`, `False`, or `None`. + + Checkbox displays a dash when its value is `None`. + """ + + autofocus: bool = False + """ + True if the control will be selected as the initial focus. If there is more than + one control on a page with autofocus set, then the first one added to the page will + get focus. + """ + + fill_color: Optional[ControlStateValue[ColorValue]] = None + """ + The [color](https://flet.dev/docs/reference/colors) that fills the checkbox in + various [`ControlState`](https://flet.dev/docs/reference/types/controlstate) states. + """ + + overlay_color: Optional[ControlStateValue[ColorValue]] = None + """ + The [color](https://flet.dev/docs/reference/colors) of the checkbox's overlay in + various [`ControlState`](https://flet.dev/docs/reference/types/controlstate) states. + + This property supports the following `ControlState` values: `PRESSED`, `SELECTED`, + `HOVERED` and `FOCUSED`. + """ + + check_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use for the check icon when + this checkbox is checked. + """ + + active_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use when this checkbox is + checked. + """ + + hover_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use when this checkbox is + hovered. + """ + + focus_color: OptionalColorValue = None + """ + TBD + """ + + semantics_label: Optional[str] = None + """ + The semantic label for the checkbox that is not shown in the UI, but will be + announced by screen readers in accessibility modes (e.g TalkBack/VoiceOver). + """ + + shape: Optional[OutlinedBorder] = None + """ + The shape of the checkbox. The value is an instance of [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder) + class. + + Defaults to `RoundedRectangleBorder(radius=2)`. + """ + + splash_radius: OptionalNumber = None + """ + The radius of the circular Material ink response (ripple) in logical pixels. + + Defaults to `20.0`. + """ + + border_side: Optional[ControlStateValue[BorderSide]] = None + """ + TBD + """ + + is_error: bool = False + """ + Whether this checkbox wants to show an error state. When `True` this checkbox will + have a different default container color and check color. + + Defaults to `False`. + """ + + visual_density: Optional[VisualDensity] = None + """ + TBD + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + The cursor to be displayed when a mouse pointer enters or is hovering over this + control. + + Value is of type [`MouseCursor`](https://flet.dev/docs/reference/types/mousecursor). + """ + + on_change: OptionalControlEventHandler["Checkbox"] = None + """ + Fires when the state of the Checkbox is changed. + """ + + on_focus: OptionalControlEventHandler["Checkbox"] = None + """ + Fires when the control has received focus. + """ + + on_blur: OptionalControlEventHandler["Checkbox"] = None + """ + Fires when the control has lost focus. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/chip.py b/sdk/python/packages/flet/src/flet/controls/material/chip.py new file mode 100644 index 000000000..789e21012 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/chip.py @@ -0,0 +1,292 @@ +from typing import Optional + +from flet.controls.animation import AnimationStyle +from flet.controls.base_control import control +from flet.controls.border import BorderSide +from flet.controls.box import BoxConstraints +from flet.controls.buttons import OutlinedBorder +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.control_state import ControlStateValue +from flet.controls.padding import OptionalPaddingValue +from flet.controls.text_style import TextStyle +from flet.controls.types import ( + ClipBehavior, + ColorValue, + OptionalColorValue, + OptionalNumber, + StrOrControl, + VisualDensity, +) + +__all__ = ["Chip"] + + +@control("Chip") +class Chip(ConstrainedControl): + """ + Chips are compact elements that represent an attribute, text, entity, or action. + + Online docs: https://flet.dev/docs/controls/chip + """ + + label: StrOrControl + """ + A `Control` that represents primary content of the chip, typically a [`Text`](https://flet.dev/docs/controls/text). + Label is a required property. + """ + + leading: Optional[Control] = None + """ + A `Control` to display to the left of the chip's `label`. + + Typically the leading control is an [`Icon`](https://flet.dev/docs/controls/icon) + or a [`CircleAvatar`](https://flet.dev/docs/controls/circleavatar). + """ + + selected: bool = False + """ + If `on_select` event is specified, `selected` property is used to determine whether + the chip is selected or not. + + Defaults to `False`. + """ + + selected_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used for the chip's background + when it is selected. + """ + + elevation: OptionalNumber = None + """ + A non-negative value which defines the size of the shadow below the chip. + + Defaults to `0`. + """ + + bgcolor: OptionalColorValue = None + """ + [Color](https://flet.dev/docs/reference/colors) to be used for the unselected, + enabled chip's background. + """ + + show_checkmark: bool = True + """ + If `on_select` event is specified and chip is selected, `show_checkmark` is used to + determine whether or not to show a checkmark. + + Defaults to `True`. + """ + + check_color: OptionalColorValue = None + """ + [Color](https://flet.dev/docs/reference/colors) of the chip's check mark when a + check mark is visible. + """ + + shadow_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used for the chip's background + when the elevation is greater than `0` and the chip is not selected. + """ + + shape: Optional[OutlinedBorder] = None + """ + The shape of the border around the chip. + + The value is an instance of [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder) + class. + + The default shape is a `StadiumBorder`. + """ + + padding: OptionalPaddingValue = None + """ + The padding between the `label` and the outside shape. + + The value is an instance of [`Padding`](https://flet.dev/docs/reference/types/padding) + class or a number. + + By default, this is 4 logical pixels on all sides. + """ + + delete_icon: Optional[Control] = None + """ + A `Control` to display to the right of the chip's `label` in case `on_delete` event + is specified. + """ + + delete_icon_tooltip: Optional[str] = None + """ + The text to be used for the chip's `delete_icon` tooltip. If not provided or + provided with an empty string, the tooltip of the delete icon will not be displayed. + """ + + delete_icon_color: OptionalColorValue = None + """ + [Color](https://flet.dev/docs/reference/colors) of the `delete_icon`. + """ + + disabled_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used for the chip's background + if it is disabled. + """ + + label_padding: OptionalPaddingValue = None + """ + Padding around the `label`. + + The value is an instance of [`padding.Padding`](https://flet.dev/docs/reference/types/padding) + class or a number. + + By default, this is 4 logical pixels at the beginning and the end of the label, and + zero on top and bottom. + """ + + label_style: Optional[TextStyle] = None + """ + The style to be applied to the chip's `label`. + + Value is of type [`TextStyle`](https://flet.dev/docs/reference/types/textstyle). + """ + + selected_shadow_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used for the chip's background + when the elevation is greater than `0` and the chip is selected. + """ + + autofocus: bool = False + """ + True if the control will be selected as the initial focus. If there is more than + one control on a page with autofocus set, then the first one added to the page will + get focus. + """ + + surface_tint_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used as an overlay on `bgcolor` + to indicate elevation. + """ + + color: Optional[ControlStateValue[ColorValue]] = None + """ + The [color](https://flet.dev/docs/reference/colors) that fills the chip in various [`ControlState`](https://flet.dev/docs/reference/types/controlstate)s. + """ + + click_elevation: OptionalNumber = None + """ + A non-negative value which defines the elevation of the chip when clicked/pressed. + + Defaults to `8.0`. + """ + + clip_behavior: ClipBehavior = ClipBehavior.NONE + """ + The content will be clipped (or not) according to this option. + + Value is of type [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) + and defaults to `ClipBehavior.NONE`. + """ + + visual_density: Optional[VisualDensity] = None + """ + TBD + """ + + border_side: Optional[BorderSide] = None + """ + Defines the color and weight of the chip's outline. + + Value is of type [`BorderSide`](https://flet.dev/docs/reference/types/borderside). + """ + + leading_size_constraints: Optional[BoxConstraints] = None + """ + The size constraints for the `leading` control. + + When unspecified, it defaults to a minimum size of chip height or label height + (whichever is greater) and a padding of 8.0 pixels on all sides. + + Value is of type [`BoxConstraints`](https://flet.dev/docs/reference/types/boxconstraints). + """ + + delete_icon_size_constraints: Optional[BoxConstraints] = None + """ + The size constraints for the `delete_icon` control. + + When unspecified, it defaults to a minimum size of chip height or label height + (whichever is greater) and a padding of 8.0 pixels on all sides. + + Value is of type [`BoxConstraints`](https://flet.dev/docs/reference/types/boxconstraints). + """ + + enable_animation_style: Optional[AnimationStyle] = None + """ + The animation style for the enable and disable animations. + + Value is of type [`AnimationStyle`](https://flet.dev/docs/reference/types/animationstyle). + """ + + select_animation_style: Optional[AnimationStyle] = None + """ + The animation style for the select and unselect animations. + + Value is of type [`AnimationStyle`](https://flet.dev/docs/reference/types/animationstyle). + """ + + leading_drawer_animation_style: Optional[AnimationStyle] = None + """ + The animation style for the `leading` control's animations. + + Value is of type [`AnimationStyle`](https://flet.dev/docs/reference/types/animationstyle). + """ + + delete_drawer_animation_style: Optional[AnimationStyle] = None + """ + The animation style for the `delete_icon`'s animations. + + Value is of type [`AnimationStyle`](https://flet.dev/docs/reference/types/animationstyle). + """ + + on_click: OptionalControlEventHandler["Chip"] = None + """ + Fires when the user clicks on the chip. Cannot be specified together with + `on_select` event. + """ + + on_delete: OptionalControlEventHandler["Chip"] = None + """ + Fires when the user clicks on the `delete_icon`. + """ + + on_select: OptionalControlEventHandler["Chip"] = None + """ + Fires when the user clicks on the chip. Changes `selected` property to the opposite + value. Cannot be specified together with `on_click` event. + """ + + on_focus: OptionalControlEventHandler["Chip"] = None + """ + Fires when the control has received focus. + """ + + on_blur: OptionalControlEventHandler["Chip"] = None + """ + Fires when the control has lost focus. + """ + + def before_update(self): + super().before_update() + assert self.on_select is None or self.on_click is None, ( + "on_select and on_click cannot be used together" + ) + assert self.elevation is None or self.elevation >= 0.0, ( + "elevation must be greater than or equal to 0" + ) + assert self.click_elevation is None or self.click_elevation >= 0.0, ( + "click_elevation must be greater than or equal to 0" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/material/circle_avatar.py b/sdk/python/packages/flet/src/flet/controls/material/circle_avatar.py new file mode 100644 index 000000000..9b2acb42c --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/circle_avatar.py @@ -0,0 +1,98 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.types import ( + OptionalColorValue, + OptionalNumber, + OptionalString, + StrOrControl, +) + +__all__ = ["CircleAvatar"] + + +@control("CircleAvatar") +class CircleAvatar(ConstrainedControl): + """ + A circle that represents a user. + + If `foreground_image_src` fails then `background_image_src` is used. If + `background_image_src` fails too, then `bgcolor` is used. + + Online docs: https://flet.dev/docs/controls/circleavatar + """ + + content: Optional[StrOrControl] = None + """ + Typically a `Text` control. If the CircleAvatar is to have an image, use + `background_image_src` instead. + """ + + foreground_image_src: OptionalString = None + """ + The source (local asset file or URL) of the foreground image in the circle. + Typically used as profile image. For fallback use `background_image_src`. + """ + + background_image_src: OptionalString = None + """ + The source (local asset file or URL) of the background image in the circle. + Changing the background image will cause the avatar to animate to the new image. + Typically used as a fallback image for `foreground_image_src`. If the CircleAvatar + is to have the user's initials, use `content` instead. + """ + + color: OptionalColorValue = None + """ + The default text [color](https://flet.dev/docs/reference/colors) for text in the + circle. Defaults to the primary text theme color if no `bgcolor` is specified. + """ + + bgcolor: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) with which to fill the circle. + Changing the background color will cause the avatar to animate to the new color. + """ + + radius: OptionalNumber = None + """ + The size of the avatar, expressed as the radius (half the diameter). If radius is + specified, then neither minRadius nor maxRadius may be specified. + """ + + min_radius: OptionalNumber = None + """ + The minimum size of the avatar, expressed as the radius (half the diameter). If + minRadius is specified, then radius must not also be specified. Defaults to zero. + """ + + max_radius: OptionalNumber = None + """ + The maximum size of the avatar, expressed as the radius (half the diameter). If + maxRadius is specified, then radius must not also be specified. Defaults to + "infinity". + """ + + on_image_error: OptionalControlEventHandler["CircleAvatar"] = None + """ + Fires when an error occurs while loading the `background_image_src` or + `foreground_image_src`. + + The event data (`e.data`) is a string whose value is either `"background"` or + `"foreground"` indicating the error's origin. + """ + + def before_update(self): + super().before_update() + assert self.radius is None or self.radius >= 0, "radius cannot be negative" + assert self.min_radius is None or self.min_radius >= 0, ( + "min_radius cannot be negative" + ) + assert self.max_radius is None or self.max_radius >= 0, ( + "max_radius cannot be negative" + ) + assert self.radius is None or ( + self.min_radius is None and self.max_radius is None + ), "If radius is set, min_radius and max_radius must be None" diff --git a/sdk/python/packages/flet/src/flet/controls/material/container.py b/sdk/python/packages/flet/src/flet/controls/material/container.py new file mode 100644 index 000000000..e94bfc73b --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/container.py @@ -0,0 +1,407 @@ +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.alignment import Alignment +from flet.controls.animation import AnimationValue +from flet.controls.base_control import control +from flet.controls.blur import BlurValue +from flet.controls.border import Border +from flet.controls.border_radius import OptionalBorderRadiusValue +from flet.controls.box import ( + BoxDecoration, + BoxShape, + ColorFilter, + DecorationImage, + ShadowValue, +) +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import ( + OptionalControlEventHandler, + OptionalEventHandler, +) +from flet.controls.events import TapEvent +from flet.controls.gradients import Gradient +from flet.controls.margin import OptionalMarginValue +from flet.controls.padding import OptionalPaddingValue +from flet.controls.theme import Theme +from flet.controls.types import ( + BlendMode, + ClipBehavior, + OptionalColorValue, + ThemeMode, + UrlTarget, +) + +__all__ = ["Container"] + + +@control("Container") +class Container(ConstrainedControl, AdaptiveControl): + """ + Container allows to decorate a control with background color and border and + position it with padding, margin and alignment. + + + + Online docs: https://flet.dev/docs/controls/container + """ + + content: Optional[Control] = None + """ + A child Control contained by the container. + """ + + padding: OptionalPaddingValue = None + """ + Empty space to inscribe inside a container decoration (background, border). The + child control is placed inside this padding. + + Value is of type [`Padding`](https://flet.dev/docs/reference/types/padding) or a + number. + """ + + margin: OptionalMarginValue = None + """ + Empty space to surround the decoration and child control. + + Value is of type [`Margin`](https://flet.dev/docs/reference/types/margin) class or + a number. + """ + + alignment: Optional[Alignment] = None + """ + Align the child control within the container. + + Value is of type [`Alignment`](https://flet.dev/docs/reference/types/alignment). + """ + + bgcolor: OptionalColorValue = None + """ + Defines the background [color](https://flet.dev/docs/reference/colors) of the + container. + """ + + gradient: Optional[Gradient] = None + """ + Defines the gradient background of the container. + + Value is of type [`Gradient`](https://flet.dev/docs/reference/types/gradient). + """ + + blend_mode: Optional[BlendMode] = None + """ + The blend mode applied to the `color` or `gradient` background of the container. + + Value is of type [`BlendMode`](https://flet.dev/docs/reference/types/blendmode) and + defaults to `BlendMode.MODULATE`. + """ + + border: Optional[Border] = None + """ + A border to draw above the background color. + + Value is of type [`Border`](https://flet.dev/docs/reference/types/border). + """ + + border_radius: OptionalBorderRadiusValue = None + """ + If specified, the corners of the container are rounded by this radius. + + Value is of type [`BorderRadius`](https://flet.dev/docs/reference/types/borderradius). + """ + + shape: Optional[BoxShape] = None + """ + Sets the shape of the container. + + Value is of type [`BoxShape`](https://flet.dev/docs/reference/types/boxshape) and + defaults to `BoxShape.RECTANGLE`. + """ + + clip_behavior: Optional[ClipBehavior] = None + """ + The content will be clipped (or not) according to this option. + + Value is of type [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) + and defaults to `ClipBehavior.ANTI_ALIAS` if `border_radius` is not `None`; + otherwise `ClipBehavior.NONE`. + """ + + ink: bool = False + """ + `True` to produce ink ripples effect when user clicks the container. + + Defaults to `False`. + """ + + image: Optional[DecorationImage] = None + """ + An image to paint above the `bgcolor` or `gradient`. If `shape=BoxShape.CIRCLE` + then this image is clipped to the circle's boundary; if `border_radius` is not + `None` then the image is clipped to the given radii. + + Value is of type [`DecorationImage`](https://flet.dev/docs/reference/types/decorationimage). + """ + + ink_color: OptionalColorValue = None + """ + The splash [color](https://flet.dev/docs/reference/colors) of the ink response. + """ + + animate: Optional[AnimationValue] = None + """ + Enables container "implicit" animation that gradually changes its values over a + period of time. + + Value is of type [`AnimationValue`](https://flet.dev/docs/reference/types/animationvalue). + """ + + blur: Optional[BlurValue] = None + """ + Applies Gaussian blur effect under the container. + + The value of this property could be one of the following: + + * **a number** - specifies the same value for horizontal and vertical sigmas, e.g. + `10`. + * **a tuple** - specifies separate values for horizontal and vertical sigmas, e.g. + `(10, 1)`. + * **an instance of [`Blur`](https://flet.dev/docs/reference/types/blur)** + + For example: + + ```python + ft.Stack( + [ + ft.Container( + content=ft.Text("Hello"), + image_src="https://picsum.photos/100/100", + width=100, + height=100, + ), + ft.Container( + width=50, + height=50, + blur=10, + bgcolor="#44CCCC00", + ), + ft.Container( + width=50, + height=50, + left=10, + top=60, + blur=(0, 10), + ), + ft.Container( + top=10, + left=60, + blur=ft.Blur(10, 0, ft.BlurTileMode.MIRROR), + width=50, + height=50, + bgcolor="#44CCCCCC", + border=ft.border.all(2, ft.Colors.BLACK), + ), + ] + ) + ``` + """ + + shadow: Optional[ShadowValue] = None + """ + Shadows cast by the container. + + Value is of type [`BoxShadow`](https://flet.dev/docs/reference/types/boxshadow) or + a `List[BoxShadow]`. + """ + + url: Optional[str] = None + """ + The URL to open when the container is clicked. If provided, `on_click` event is + fired after that. + """ + + url_target: Optional[UrlTarget] = None + """ + Where to open URL in the web mode. + + Value is of type [`UrlTarget`](https://flet.dev/docs/reference/types/urltarget) and + defaults to `UrlTarget.BLANK`. + """ + + theme: Optional[Theme] = None + """ + Allows setting a nested `theme` for all controls inside the container and down the + tree. + + Value is of type [`Theme`](https://flet.dev/docs/cookbook/theming). + + **Usage example** + + ```python + import flet as ft + + def main(page: ft.Page): + # Yellow page theme with SYSTEM (default) mode + page.theme = ft.Theme( + color_scheme_seed=ft.Colors.YELLOW, + ) + + page.add( + # Page theme + ft.Container( + content=ft.ElevatedButton("Page theme button"), + bgcolor=ft.Colors.SURFACE_CONTAINER_HIGHEST, + padding=20, + width=300, + ), + + # Inherited theme with primary color overridden + ft.Container( + theme=ft.Theme(color_scheme=ft.ColorScheme(primary=ft.Colors.PINK)), + content=ft.ElevatedButton("Inherited theme button"), + bgcolor=ft.Colors.SURFACE_CONTAINER_HIGHEST, + padding=20, + width=300, + ), + + # Unique always DARK theme + ft.Container( + theme=ft.Theme(color_scheme_seed=ft.Colors.INDIGO), + theme_mode=ft.ThemeMode.DARK, + content=ft.ElevatedButton("Unique theme button"), + bgcolor=ft.Colors.SURFACE_CONTAINER_HIGHEST, + padding=20, + width=300, + ), + ) + + ft.app(main) + ``` + """ + + dark_theme: Optional[Theme] = None + """ + Allows setting a nested `theme` to be used when in dark theme mode for all controls + inside the container and down the tree. + + Value is of type [`Theme`](https://flet.dev/docs/cookbook/theming). + """ + + theme_mode: Optional[ThemeMode] = None + """ + Setting `theme_mode` "resets" parent theme and creates a new, unique scheme for all + controls inside the container. Otherwise the styles defined in container's `theme` + property override corresponding styles from the parent, inherited theme. + + Value is of type [`ThemeMode`](https://flet.dev/docs/reference/types/thememode) and + defaults to `ThemeMode.SYSTEM`. + """ + + color_filter: Optional[ColorFilter] = None + """ + Applies a color filter to the container. + + Value is of type [`ColorFilter`](https://flet.dev/docs/reference/types/colorfilter). + """ + + ignore_interactions: bool = False + """ + Whether to ignore all interactions with this container and its descendants. + + Defaults to `False`. + """ + + foreground_decoration: Optional[BoxDecoration] = None + """ + The foreground decoration. + + Value is of type [`BoxDecoration`](https://flet.dev/docs/reference/types/boxdecoration). + """ + + on_click: OptionalControlEventHandler["Container"] = None + """ + Fires when a user clicks the container. Will not be fired on long press. + """ + + on_tap_down: OptionalEventHandler[TapEvent["Container"]] = None + """ + Fires when a user clicks the container with or without a long press. + + Event handler argument is of type [`TapEvent`](https://flet.dev/docs/reference/types/tapevent). + + Info: + If `ink` is `True`, `e` will be plain `ControlEvent` with empty `data` instead of + `ContainerTapEvent`. + + A simple usage example: + + ```python + import flet as ft + + def main(page: ft.Page): + page.vertical_alignment = ft.MainAxisAlignment.CENTER + page.horizontal_alignment = ft.CrossAxisAlignment.CENTER + + def on_long_press(e): + print("on long press") + page.add(ft.Text("on_long_press triggered")) + + def on_click(e): + print("on click") + page.add(ft.Text("on_click triggered")) + + def on_tap_down(e: ft.ContainerTapEvent): + print("on tap down", e.local_x, e.local_y) + page.add(ft.Text("on_tap_down triggered")) + + c = ft.Container( + bgcolor=ft.Colors.RED, + content=ft.Text("Test Long Press"), + height=100, + width=100, + on_click=on_click, + on_long_press=on_long_press, + on_tap_down=on_tap_down, + ) + + page.add(c) + + ft.app(main) + ``` + """ + + on_long_press: OptionalControlEventHandler["Container"] = None + """ + Fires when the container is long-pressed. + """ + + on_hover: OptionalControlEventHandler["Container"] = None + """ + Fires when a mouse pointer enters or exists the container area. `data` property of + event object contains `true` (string) when cursor enters and `false` when it exits. + + A simple example of a container changing its background color on mouse hover: + + ```python + import flet as ft + + def main(page: ft.Page): + def on_hover(e): + e.control.bgcolor = "blue" if e.data == True else "red" + e.control.update() + + page.add( + ft.Container( + width=100, + height=100, + bgcolor="red", + ink=False, + on_hover=on_hover, + ) + ) + + ft.app(main) + ``` + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/datatable.py b/sdk/python/packages/flet/src/flet/controls/material/datatable.py new file mode 100644 index 000000000..1f6076f3e --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/datatable.py @@ -0,0 +1,503 @@ +from dataclasses import dataclass, field +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.border import Border, BorderSide +from flet.controls.border_radius import OptionalBorderRadiusValue +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import ( + Event, + OptionalControlEventHandler, + OptionalEventHandler, +) +from flet.controls.control_state import ControlStateValue +from flet.controls.events import TapEvent +from flet.controls.gradients import Gradient +from flet.controls.text_style import TextStyle +from flet.controls.types import ( + ClipBehavior, + ColorValue, + MainAxisAlignment, + Number, + OptionalColorValue, + OptionalNumber, + StrOrControl, +) + + +@dataclass +class DataColumnSortEvent(Event["DataColumn"]): + column_index: int = field(metadata={"data_field": "ci"}) + ascending: bool = field(metadata={"data_field": "asc"}) + + +@control("DataColumn") +class DataColumn(Control): + """ + Column configuration for a `DataTable`. + + One column configuration must be provided for each column to display in the table. + """ + + label: StrOrControl + """ + The column heading. + + Typically, this will be a `Text` control. It could also be an `Icon` (typically + using size 18), or a `Row` with an icon and some text. + """ + + tooltip_text: Optional[str] = None + """ + The column heading's tooltip. + + This is a longer description of the column heading, for cases where the heading + might have been abbreviated to keep the column width to a reasonable size. + """ + + numeric: bool = False + """ + Whether this column represents numeric data or not. + + The contents of cells of columns containing numeric data are right-aligned. + """ + + column_tooltip: Optional[str] = None + # No reference documentation provided for `column_tooltip`. + + heading_row_alignment: Optional[MainAxisAlignment] = None + """ + Defines the horizontal layout of the label and sort indicator in the heading row. + + Value is of type [`MainAxisAlignment`](https://flet.dev/docs/reference/types/mainaxisalignment). + """ + + on_sort: OptionalEventHandler[DataColumnSortEvent] = None + """ + Called when the user asks to sort the table using this column. + + If not set, the column will not be considered sortable. + """ + + def before_update(self): + super().before_update() + assert isinstance(self.label, str) or ( + isinstance(self.label, Control) and self.label.visible + ), "label must be visible" + + +@control("DataCell") +class DataCell(Control): + """ + The data for a cell of a `DataTable`. + + One list of DataCell objects must be provided for each `DataRow` in the `DataTable`. + """ + + content: StrOrControl + """ + The data for the row. + + Typically a `Text` control or a `Dropdown` control. + + If the cell has no data, then a `Text` widget with placeholder text should be + provided instead, and `placeholder` should be set to `True`. + + This control can only have one child. To lay out multiple children, let this + control's child be a widget such as `Row`, `Column`, or `Stack`, which have + `controls` property, and then provide the children to that widget. + """ + + placeholder: bool = False + """ + Whether the child is actually a placeholder. + + If this is `True`, the default text style for the cell is changed to be appropriate + for placeholder text. + """ + + show_edit_icon: bool = False + """ + Whether to show an edit icon at the end of the cell. + + This does not make the cell actually editable; the caller must implement editing + behavior if desired (initiated from the `on_tap` callback). + + If this is set, `on_tap` should also be set, otherwise tapping the icon will have + no effect. + """ + + on_tap: OptionalControlEventHandler["DataCell"] = None + """ + Called if the cell is tapped. + + If specified, tapping the cell will call this callback, else tapping the cell will + attempt to select the row (if `DataRow.on_select_changed` is provided). + """ + + on_double_tap: OptionalControlEventHandler["DataCell"] = None + """ + Called when the cell is double tapped. + + If specified, tapping the cell will call this callback, else (tapping the cell will + attempt to select the row (if `DataRow.on_select_changed` is provided). + """ + + on_long_press: OptionalControlEventHandler["DataCell"] = None + """ + Called if the cell is long-pressed. + + If specified, tapping the cell will invoke this callback, else tapping the cell + will attempt to select the row (if `DataRow.on_select_changed` is provided). + """ + + on_tap_cancel: OptionalControlEventHandler["DataCell"] = None + """ + Called if the user cancels a tap was started on cell. + + If specified, cancelling the tap gesture will invoke this callback, else tapping + the cell will attempt to select the row (if `DataRow.on_select_changed` is + provided). + """ + + on_tap_down: OptionalEventHandler[TapEvent["DataCell"]] = None + """ + Called if the cell is tapped down. + + If specified, tapping the cell will call this callback, else tapping the cell will + attempt to select the row (if `DataRow.on_select_changed` is provided). + """ + + def before_update(self): + super().before_update() + assert self.content.visible, "content must be visible" + + +@control("DataRow") +class DataRow(Control): + """ + Row configuration and cell data for a DataTable. + + One row configuration must be provided for each row to display in the table. + + The data for this row of the table is provided in the `cells` property of the + `DataRow` object. + """ + + cells: list[DataCell] = field(default_factory=list) + """ + The data for this row - a list of [`DataCell`](https://flet.dev/docs/reference/datacell) + controls. + + There must be exactly as many cells as there are columns in the table. + """ + + color: Optional[ControlStateValue[ColorValue]] = None + """ + The [color](https://flet.dev/docs/reference/colors) for the row. + + By default, the color is transparent unless selected. Selected rows has a grey + translucent color. + + The effective color can depend on the [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + state, if the row is selected, pressed, hovered, focused, disabled or enabled. The + color is painted as an overlay to the row. To make sure that the row's InkWell is + visible (when pressed, hovered and focused), it is recommended to use a translucent + color. + """ + + selected: bool = False + """ + Whether the row is selected. + + If `on_select_changed` is non-null for any row in the table, then a checkbox is + shown at the start of each row. If the row is selected (`True`), the checkbox will + be checked and the row will be highlighted. + + Otherwise, the checkbox, if present, will not be checked. + """ + + on_long_press: OptionalControlEventHandler["DataRow"] = None + """ + Called if the row is long-pressed. + + If a `DataCell` in the row has its `DataCell.on_tap`, `DataCell.on_double_tap`, + `DataCell.on_long_press`, `DataCell.on_tap_cancel` or `DataCell.on_tap_down` + callback defined, that callback behavior overrides the gesture behavior of the row + for that particular cell. + """ + + on_select_changed: OptionalControlEventHandler["DataRow"] = None + """ + Called when the user selects or unselects a selectable row. + + If this is not null, then the row is selectable. The current selection state of the + row is given by selected. + + If any row is selectable, then the table's heading row will have a checkbox that + can be checked to select all selectable rows (and which is checked if all the rows + are selected), and each subsequent row will have a checkbox to toggle just that row. + + A row whose `on_select_changed` callback is null is ignored for the purposes of + determining the state of the "all" checkbox, and its checkbox is disabled. + + If a `DataCell` in the row has its `DataCell.on_tap` callback defined, that + callback behavior overrides the gesture behavior of the row for that particular + cell. + """ + + def __contains__(self, item): + return item in self.cells + + def before_update(self): + super().before_update() + assert any(cell.visible for cell in self.cells), ( + "cells must contain at minimum one visible DataCell" + ) + + +@control("DataTable") +class DataTable(ConstrainedControl): + """ + A Material Design data table. + + Online docs: https://flet.dev/docs/controls/datatable + """ + + columns: list[DataColumn] + """ + A list of [DataColumn](https://flet.dev/docs/controls/datatable#datacolumn) + controls describing table columns. + """ + + rows: list[DataRow] = field(default_factory=list) + """ + A list of [DataRow](https://flet.dev/docs/controls/datatable#datarow) controls + defining table rows. + """ + + sort_ascending: bool = False + """ + Whether the column mentioned in `sort_column_index`, if any, is sorted in ascending + order. + + If `True`, the order is ascending (meaning the rows with the smallest values for + the current sort column are first in the table). + + If `False`, the order is descending (meaning the rows with the smallest values for + the current sort column are last in the table). + """ + + show_checkbox_column: bool = False + """ + Whether the control should display checkboxes for selectable rows. + + If `True`, a `Checkbox` will be placed at the beginning of each row that is + selectable. However, if `DataRow.on_select_changed` is not set for any row, + checkboxes will not be placed, even if this value is `True`. + + If `False`, all rows will not display a `Checkbox`. + """ + + sort_column_index: Optional[int] = None + """ + The current primary sort key's column. + + If specified, indicates that the indicated column is the column by which the data + is sorted. The number must correspond to the index of the relevant column in + `columns`. + + Setting this will cause the relevant column to have a sort indicator displayed. + + When this is `None`, it implies that the table's sort order does not correspond to + any of the columns. + """ + + show_bottom_border: bool = False + """ + Whether a border at the bottom of the table is displayed. + + By default, a border is not shown at the bottom to allow for a border around the + table defined by decoration. + """ + + border: Optional[Border] = None + """ + The border around the table. + + The value is an instance of [Border](https://flet.dev/docs/reference/types/border) + class. + """ + + border_radius: OptionalBorderRadiusValue = None + """ + Border corners. + + Border radius is an instance of [BorderRadius](https://flet.dev/docs/reference/types/borderradius) + class. + """ + + horizontal_lines: Optional[BorderSide] = None + """ + Set the [color](https://flet.dev/docs/reference/colors) and width of horizontal + lines between rows. An instance of [BorderSide](https://flet.dev/docs/reference/types/borderside) + class. + """ + + vertical_lines: Optional[BorderSide] = None + """ + Set the [color](https://flet.dev/docs/reference/colors) and width of vertical lines + between columns. + + Value is of type [BorderSide](https://flet.dev/docs/reference/types/borderside). + """ + + checkbox_horizontal_margin: OptionalNumber = None + """ + Horizontal margin around the checkbox, if it is displayed. + """ + + column_spacing: OptionalNumber = None + """ + The horizontal margin between the contents of each data column. + """ + + data_row_color: Optional[ControlStateValue[ColorValue]] = None + """ + The background [color](https://flet.dev/docs/reference/colors) for the data rows. + + The effective background color can be made to depend on the [ControlState](https://flet.dev/docs/reference/types/controlstate) + state, i.e. if the row is selected, pressed, hovered, focused, disabled or enabled. + The color is painted as an overlay to the row. To make sure that the row's InkWell + is visible (when pressed, hovered and focused), it is recommended to use a + translucent background color. + """ + + data_row_min_height: OptionalNumber = None + """ + The minimum height of each row (excluding the row that contains column headings). + + Defaults to `48.0` and must be less than or equal to `data_row_max_height`. + """ + + data_row_max_height: OptionalNumber = None + """ + The maximum height of each row (excluding the row that contains column headings). + Set to `float("inf")` for the height of each row to adjust automatically with its + content. + + Defaults to `48.0` and must be greater than or equal to `data_row_min_height`. + """ + + data_text_style: Optional[TextStyle] = None + """ + The text style for data rows. An instance of [TextStyle](https://flet.dev/docs/reference/types/textstyle) + class. + """ + + bgcolor: OptionalColorValue = None + """ + The background [color](https://flet.dev/docs/reference/colors) for the table. + """ + + gradient: Optional[Gradient] = None + """ + The background gradient for the table. + + Value is of type [Gradient](https://flet.dev/docs/reference/types/gradient). + """ + + divider_thickness: Number = 1.0 + """ + The width of the divider that appears between `TableRow`s. Must be greater than or + equal to zero. + + Defaults to 1.0. + """ + + heading_row_color: Optional[ControlStateValue[ColorValue]] = None + """ + The background [color](https://flet.dev/docs/reference/colors) for the heading row. + + The effective background color can be made to depend on the [ControlState](https://flet.dev/docs/reference/types/controlstate) + state, i.e. if the row is pressed, hovered, focused when sorted. The color is + painted as an overlay to the row. To make sure that the row's InkWell is visible + (when pressed, hovered and focused), it is recommended to use a translucent color. + """ + + heading_row_height: OptionalNumber = None + """ + The height of the heading row. + """ + + heading_text_style: Optional[TextStyle] = None + """ + The text style for the heading row. An instance of [TextStyle](https://flet.dev/docs/reference/types/textstyle) + class. + """ + + horizontal_margin: OptionalNumber = None + """ + The horizontal margin between the edges of the table and the content in the first + and last cells of each row. + + When a checkbox is displayed, it is also the margin between the checkbox the + content in the first data column. + """ + + clip_behavior: ClipBehavior = ClipBehavior.NONE + """ + The content will be clipped (or not) according to this option. + + Value is of type [ClipBehavior](https://flet.dev/docs/reference/types/clipbehavior) + and defaults to `ClipBehavior.ANTI_ALIAS` if `border_radius!=None`; otherwise + `ClipBehavior.HARD_EDGE`. + """ + + on_select_all: OptionalControlEventHandler["DataTable"] = None + """ + Invoked when the user selects or unselects every row, using the checkbox in the + heading row. + + If this is `None`, then the `DataRow.on_select_changed` callback of every row in + the table is invoked appropriately instead. + + To control whether a particular row is selectable or not, see + `DataRow.on_select_changed`. This callback is only relevant if any row is + selectable. + """ + + def __contains__(self, item): + return item in self.columns or item in self.rows + + def before_update(self): + super().before_update() + visible_columns = list(filter(lambda column: column.visible, self.columns)) + visible_rows = list(filter(lambda row: row.visible, self.rows)) + assert len(visible_columns) > 0, ( + "columns must contain at minimum one visible DataColumn" + ) + assert all( + [ + len([c for c in row.cells if c.visible]) == len(visible_columns) + for row in visible_rows + ] + ), ( + f"each visible DataRow must contain exactly as many visible DataCells as " + f"there are visible DataColumns ({len(visible_columns)})" + ) + assert ( + self.data_row_min_height is None + or self.data_row_max_height is None + or (self.data_row_min_height <= self.data_row_max_height) + ), "data_row_min_height must be less than or equal to data_row_max_height" + assert self.divider_thickness is None or self.divider_thickness >= 0, ( + "divider_thickness must be greater than or equal to 0" + ) + assert self.sort_column_index is None or ( + 0 <= self.sort_column_index < len(visible_columns) + ), ( + f"sort_column_index must be greater than or equal to 0 and less than the " + f"number of visible columns ({len(visible_columns)})" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/material/date_picker.py b/sdk/python/packages/flet/src/flet/controls/material/date_picker.py new file mode 100644 index 000000000..876827985 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/date_picker.py @@ -0,0 +1,202 @@ +from dataclasses import dataclass, field +from datetime import datetime +from enum import Enum +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import ( + Event, + OptionalControlEventHandler, + OptionalEventHandler, +) +from flet.controls.dialog_control import DialogControl +from flet.controls.duration import DateTimeValue +from flet.controls.material.textfield import KeyboardType +from flet.controls.types import ( + IconValue, + OptionalColorValue, +) + +__all__ = [ + "DatePicker", + "DatePickerMode", + "DatePickerEntryMode", + "DatePickerEntryModeChangeEvent", +] + + +class DatePickerMode(Enum): + DAY = "day" + YEAR = "year" + + +class DatePickerEntryMode(Enum): + CALENDAR = "calendar" + INPUT = "input" + CALENDAR_ONLY = "calendarOnly" + INPUT_ONLY = "inputOnly" + + +@dataclass +class DatePickerEntryModeChangeEvent(Event["DatePicker"]): + entry_mode: Optional[DatePickerEntryMode] + + +@control("DatePicker") +class DatePicker(DialogControl): + """ + A Material-style date picker dialog. + + It is added to [`page.overlay`](https://flet.dev/page#overlay) and can be opened by + calling `Page.open_dialog()` method. + + Depending on the `date_picker_entry_mode`, it will show either a Calendar or an + Input (TextField) for picking a date. + + Online docs: https://flet.dev/docs/controls/datepicker + """ + + value: Optional[DateTimeValue] = None + """ + The selected date that the picker should display. + + Defaults to `current_date`. + """ + + modal: bool = False + """ + TBD + """ + + first_date: DateTimeValue = field( + default_factory=lambda: datetime(year=1900, month=1, day=1) + ) + """ + The earliest allowable date that the user can select. Defaults to `January 1, 1900`. + """ + + last_date: DateTimeValue = field( + default_factory=lambda: datetime(year=2050, month=1, day=1) + ) + """ + The latest allowable date that the user can select. Defaults to `January 1, 2050`. + """ + + current_date: DateTimeValue = field(default_factory=lambda: datetime.now()) + """ + The date representing today. It will be highlighted in the day grid. + """ + + keyboard_type: KeyboardType = KeyboardType.DATETIME + """ + The type of keyboard to use for editing the text. + + Value is of type [`KeyboardType`](https://flet.dev/docs/reference/types/keyboardtype) + and defaults to `KeyboardType.DATETIME`. + """ + + date_picker_mode: DatePickerMode = DatePickerMode.DAY + """ + Initial display of a calendar date picker. + + Value is of type [`DatePickerMode`](https://flet.dev/docs/reference/types/datepickermode) + and defaults to `DatePickerMode.DAY`. + """ + + date_picker_entry_mode: DatePickerEntryMode = DatePickerEntryMode.CALENDAR + """ + The initial mode of date entry method for the date picker dialog. + + Value is of type [`DatePickerEntryMode`](https://flet.dev/docs/reference/types/datepickerentrymode) + and defaults to `DatePickerEntryMode.CALENDAR`. + """ + + help_text: Optional[str] = None + """ + The text that is displayed at the top of the header. + + This is used to indicate to the user what they are selecting a date for. + + Defaults to `"Select date"`. + """ + + cancel_text: Optional[str] = None + """ + The text that is displayed on the cancel button. Defaults to `"Cancel"`. + """ + + confirm_text: Optional[str] = None + """ + The text that is displayed on the confirm button. Defaults to `"OK"`. + """ + + error_format_text: Optional[str] = None + """ + The error message displayed below the TextField if the entered date is not in the + correct format. + + Defaults to `"Invalid format"`. + """ + + error_invalid_text: Optional[str] = None + """ + The error message displayed below the TextField if the date is earlier than + `first_date` or later than `last_date`. + + Defaults to `"Out of range"`. + """ + + field_hint_text: Optional[str] = None + """ + The hint text displayed in the text field. + + The default value is the date format string that depends on your locale. For + example, 'mm/dd/yyyy' for en_US. + """ + + field_label_text: Optional[str] = None + """ + The label text displayed in the TextField. + + Defaults to `"Enter Date"`. + """ + + switch_to_calendar_icon: Optional[IconValue] = None + """ + Name of the icon displayed in the corner of the dialog when `DatePickerEntryMode` + is `DatePickerEntryMode.INPUT`. + Clicking on icon changes the `DatePickerEntryMode` to + `DatePickerEntryMode.CALENDAR`. If `None`, `icons.CALENDAR_TODAY` is used. + """ + + switch_to_input_icon: Optional[IconValue] = None + """ + Name of the icon displayed in the corner of the dialog when `DatePickerEntryMode` + is `DatePickerEntryMode.CALENDAR`. + Clicking on icon changes the `DatePickerEntryMode` to `DatePickerEntryMode.INPUT`. + If `None`, `icons.EDIT_OUTLINED` is used. + """ + + barrier_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the modal barrier that + darkens everything below the date picker. + + If `None`, the [`DialogTheme.barrier_color`](https://flet.dev/docs/reference/types/dialogtheme#barrier_color) + is used. + If it is also `None`, then `Colors.BLACK_54` is used. + """ + + on_change: OptionalControlEventHandler["DatePicker"] = None + """ + Fires when user clicks confirm button. `value` property is updated with selected + date. `e.data` also contains the selected date. + """ + + on_entry_mode_change: OptionalEventHandler[DatePickerEntryModeChangeEvent] = None + """ + Fires when the `date_picker_entry_mode` is changed. + + Event handler argument is of + type [`DatePickerEntryModeChangeEvent`](https://flet.dev/docs/reference/types/datepickerentrymodechangeevent). + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/divider.py b/sdk/python/packages/flet/src/flet/controls/material/divider.py new file mode 100644 index 000000000..0e2f42676 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/divider.py @@ -0,0 +1,73 @@ +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.types import OptionalColorValue, OptionalNumber + +__all__ = ["Divider"] + + +@control("Divider") +class Divider(Control): + """ + A thin horizontal line, with padding on either side. + + In the material design language, this represents a divider. + + Online docs: https://flet.dev/docs/controls/divider + """ + + color: OptionalColorValue = None + """ + The color to use when painting the line. + + If this is `None`, then the `DividerTheme.color` is used. If that is also `None`, + then the `Theme.divider_color` is used. + """ + + height: OptionalNumber = None + """ + The divider's height extent. The divider itself is always drawn as a horizontal + line that is centered within the height specified by this value. + + If this is `None`, then the `DividerTheme.space` is used. If that is also `None`, + then `16.0` is used. + """ + + leading_indent: OptionalNumber = None + """ + The amount of empty space to the leading edge of the divider. + + If this is `None`, then the `DividerTheme.leading_indent` is used. If that is also + `None`, then `0.0` is used. + """ + + thickness: OptionalNumber = None + """ + The thickness of the line drawn within the divider. A divider with a thickness of + `0.0` is always drawn as a line with a height of exactly one device pixel. + + If this is `None`, then the `DividerTheme.thickness` is used. If that is also + `None`, then `0.0` is used. + """ + + trailing_indent: OptionalNumber = None + """ + The amount of empty space to the trailing edge of the divider. + + If this is `None`, then the `DividerTheme.trailing_indent` is used. If that is also + `None`, then `0.0` is used. + """ + + def before_update(self): + super().before_update() + assert ( + self.height is None or self.height >= 0 + ), "height must be greater than or equal to 0" + assert ( + self.thickness is None or self.thickness >= 0 + ), "thickness must be greater than or equal to 0" + assert ( + self.leading_indent is None or self.leading_indent >= 0 + ), "leading_indent must be greater than or equal to 0" + assert ( + self.trailing_indent is None or self.trailing_indent >= 0 + ), "trailing_indent must be greater than or equal to 0" diff --git a/sdk/python/packages/flet/src/flet/controls/material/dropdown.py b/sdk/python/packages/flet/src/flet/controls/material/dropdown.py new file mode 100644 index 000000000..0a8eb7e01 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/dropdown.py @@ -0,0 +1,377 @@ +import asyncio +from dataclasses import field +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.border_radius import OptionalBorderRadiusValue +from flet.controls.buttons import ButtonStyle +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.control_state import ControlStateValue +from flet.controls.material.form_field_control import InputBorder +from flet.controls.material.icons import Icons +from flet.controls.material.textfield import InputFilter, TextCapitalization +from flet.controls.padding import OptionalPaddingValue +from flet.controls.text_style import TextStyle +from flet.controls.types import ( + ColorValue, + IconValueOrControl, + Number, + OptionalColorValue, + OptionalNumber, + StrOrControl, + TextAlign, +) + +__all__ = ["Dropdown", "DropdownOption"] + + +@control("Option") +class Option(Control): + """ + Represents an item in a dropdown. Either `key` or `text` must be specified, else an + `AssertionError` will be raised. + """ + + key: Optional[str] = None + """ + Option's key. If not specified `text` will be used as fallback. + """ + + text: Optional[str] = None + """ + Option's display text. If not specified `key` will be used as fallback. + """ + + content: Optional[Control] = None + """ + A `Control` to display in this option. If not specified, `text` will be used as + fallback, else `text` will be ignored. + """ + + leading_icon: Optional[IconValueOrControl] = None + """ + An optional icon to display before the content or text. + """ + + trailing_icon: Optional[IconValueOrControl] = None + """ + An optional icon to display after the content or text. + """ + + style: Optional[ButtonStyle] = None + """ + Customizes this menu item's appearance. + + The value is an instance of [`ButtonStyle`](https://flet.dev/docs/reference/types/buttonstyle) + class. + """ + + def before_update(self): + super().before_update() + assert self.key is not None or self.text is not None, ( + "key or text must be specified" + ) + + +DropdownOption = Option + + +@control("Dropdown") +class Dropdown(ConstrainedControl): + """ + A dropdown control that allows users to select a single option from a list of + options. + + Online docs: https://flet.dev/docs/controls/dropdown + """ + + value: Optional[str] = None + """ + `key` value of the selected option. + """ + + options: list[Option] = field(default_factory=list) + """ + A list of `DropdownOption` controls representing items in this dropdown. + """ + + autofocus: bool = False + """ + True if the control will be selected as the initial focus. If there is more than + one control on a page with autofocus set, then the first one added to the page will + get focus. + """ + + text_align: TextAlign = TextAlign.START + """ + The text align for the TextField of the Dropdown. + + Value is of type [`TextAlign`](https://flet.dev/docs/reference/types/textalign) and + defaults to `TextAlign.START`. + """ + + elevation: Optional[ControlStateValue[OptionalNumber]] = 8 + """ + The dropdown's menu elevation in various [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + states. + + Defaults to `8`. + """ + + enable_filter: bool = False + """ + Determine if the menu list can be filtered by the text input. Defaults to false. + + If set to true, dropdown menu will show a filtered list. The filtered list will + contain items that match the text provided by the input field, with a + case-insensitive comparison. + """ + + enable_search: bool = True + """ + Determine if the first item that matches the text input can be highlighted. + + Defaults to true as the search function could be commonly used. + """ + + editable: bool = False + """ + TBD + """ + + menu_height: OptionalNumber = None + """ + The height of the dropdown menu. If this is null, the menu will display as many + items as possible on the screen. + """ + + menu_width: OptionalNumber = None + """ + The width of the dropdown menu. If this is null, the menu width will be the same as + input textfield width. + """ + + expanded_insets: OptionalPaddingValue = None + """ + TBD + """ + + selected_suffix: Optional[Control] = None + """ + TBD + """ + + input_filter: Optional[InputFilter] = None + """ + TBD + """ + + capitalization: Optional[TextCapitalization] = None + """ + TBD + """ + + trailing_icon: IconValueOrControl = Icons.ARROW_DROP_DOWN + """ + An optional icon at the end of the text field (previously, + [`select_icon`](#select_icon)). + + Defaults to an Icon with `ft.Icons.ARROW_DROP_DOWN`. + """ + + leading_icon: Optional[IconValueOrControl] = None + """ + An optional Icon at the front of the text input field inside the decoration box + (previously, [`prefix_icon`](#prefix_icon)). + + Defaults to null. If this is not null, the menu items will have extra paddings to + be aligned with the text in the text field. + """ + + selected_trailing_icon: IconValueOrControl = Icons.ARROW_DROP_UP + """ + An optional icon at the end of the text field to indicate that the text field is + pressed. + + Defaults to an Icon with `ft.Icons.ARROW_DROP_UP`. + """ + + bgcolor: Optional[ControlStateValue[ColorValue]] = None + """ + The background [color](https://flet.dev/docs/reference/colors) of the dropdown menu + in various [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + states. + """ + + on_change: OptionalControlEventHandler["Dropdown"] = None + """ + Fires when the selected item of this dropdown has changed. + """ + + on_focus: OptionalControlEventHandler["Dropdown"] = None + """ + Fires when the control has received focus. + """ + + on_blur: OptionalControlEventHandler["Dropdown"] = None + """ + Fires when the control has lost focus. + """ + + # From FormField + + error_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) to use for + `error_text`. + """ + + error_text: Optional[str] = None + """ + Text that appears below the input border. + + If non-null, the border's color animates to red and the `helper_text` is not shown. + """ + + text_size: OptionalNumber = None + """ + Text size in virtual pixels. + """ + + text_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) to use for text + in input text field. + """ + + label: Optional[StrOrControl] = None + """ + Optional text that describes the input field. + + When the input field is empty and unfocused, the label is displayed on top of the + input field (i.e., at the same location on the screen where text may be entered in + the input field). When the input field receives focus (or if the field is + non-empty) the label moves above, either vertically adjacent to, or to the center + of the input field. + """ + + label_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) to use for + `label`. + """ + + border: Optional[InputBorder] = None + """ + Border around input. + + Value is of type [`InputBorder`](https://flet.dev/docs/reference/types/inputborder) + and defaults to `InputBorder.OUTLINE`. + """ + + color: OptionalColorValue = None + """ + Text [color](https://flet.dev/docs/reference/colors). + """ + + border_width: Number = 1 + """ + The width of the border in virtual pixels. Set to `0` to completely remove border. + + Defaults to `1`. + """ + + border_color: OptionalColorValue = None + """ + Border [color](https://flet.dev/docs/reference/colors). Could be `transparent` to + hide the border. + """ + + border_radius: OptionalBorderRadiusValue = None + """ + Border radius is an instance of [`BorderRadius`](https://flet.dev/docs/reference/types/borderradius) + class. + """ + + focused_border_width: OptionalNumber = None + """ + Border width in focused state. + """ + + focused_border_color: OptionalColorValue = None + """ + Border [color](https://flet.dev/docs/reference/colors) in focused state. + """ + + content_padding: OptionalPaddingValue = None + """ + The [padding](https://flet.dev/docs/reference/types/padding) for the input + decoration's container. + """ + + dense: bool = False + """ + Whether the TextField is part of a dense form (ie, uses less vertical space). + """ + + filled: bool = False + """ + If `True` the decoration's container is filled with theme `fill_color`. The default + is `False`. + """ + + fill_color: OptionalColorValue = None + """ + Background [color](https://flet.dev/docs/reference/colors) of the dropdown input + text field. Will not be visible if `filled=False`. + """ + + hover_color: OptionalColorValue = None + """ + TBD + """ + + hint_text: Optional[str] = None + """ + Text that suggests what sort of input the field accepts. + + Displayed on top of the input when it's empty and either (a) `label` is null or (b) + the input has the focus. + """ + + hint_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) to use for + `hint_text`. + """ + + helper_text: Optional[str] = None + """ + Text that provides context about the input's value, such as how the value will be + used. + + If non-null, the text is displayed below the input decorator, in the same location + as `error_text`. If a non-null `error_text` value is specified then the helper text + is not shown. + """ + + helper_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) to use for + `helper_text`. + """ + + def before_update(self): + super().before_update() + self.expand_loose = self.expand # to fix a display issue + + def __contains__(self, item): + return item in self.options + + async def focus_async(self): + await self._invoke_method_async("focus") + + def focus(self): + asyncio.create_task(self.focus_async()) diff --git a/sdk/python/packages/flet/src/flet/controls/material/dropdownm2.py b/sdk/python/packages/flet/src/flet/controls/material/dropdownm2.py new file mode 100644 index 000000000..443a0a97c --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/dropdownm2.py @@ -0,0 +1,220 @@ +from typing import Optional + +from flet.controls.alignment import Alignment +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.material.form_field_control import FormFieldControl +from flet.controls.padding import OptionalPaddingValue +from flet.controls.text_style import TextStyle +from flet.controls.types import ( + IconValueOrControl, + Number, + OptionalColorValue, + OptionalNumber, +) + +__all__ = ["DropdownM2", "Option"] + + +@control("Option") +class Option(Control): + """ + Represents an item in a dropdown. Either `key` or `text` must be specified, else an + `AssertionError` will be raised. + """ + + key: Optional[str] = None + """ + Option's key. If not specified `text` will be used as fallback. + """ + + text: Optional[str] = None + """ + Option's display text. If not specified `key` will be used as fallback. + """ + + content: Optional[Control] = None + """ + A `Control` to display in this option. If not specified, `text` will be used as + fallback, else `text` will be ignored. + """ + + alignment: Optional[Alignment] = None + """ + Defines the alignment of this option in it's container. + + Value is of type [`Alignment`](https://flet.dev/docs/reference/types/alignment) and + defaults to `Alignment.center_left()`. + """ + + text_style: Optional[TextStyle] = None + """ + Defines the style of the `text`. + + Value is of type [`TextStyle`](https://flet.dev/docs/reference/types/textstyle). + """ + + on_click: OptionalControlEventHandler["Option"] = None + """ + Fires when this option is clicked. + """ + + def before_update(self): + super().before_update() + assert self.key is not None or self.text is not None, ( + "key or text must be specified" + ) + + +@control("DropdownM2") +class DropdownM2(FormFieldControl): + """ + A dropdown lets the user select from a number of items. The dropdown shows the + currently selected item as well as an arrow that opens a menu for selecting another + item. + + Online docs: https://flet.dev/docs/controls/dropdown + """ + + value: Optional[str] = None + """ + `key` value of the selected option. + """ + + options: Optional[list[Option]] = None + """ + A list of `Option` controls representing items in this dropdown. + """ + + alignment: Optional[Alignment] = None + """ + Defines how the `hint` or the selected item is positioned within this dropdown. + + Alignment is an instance of [`Alignment`](https://flet.dev/docs/reference/types/alignment) + class. + """ + + autofocus: bool = False + """ + True if the control will be selected as the initial focus. If there is more than + one control on a page with autofocus set, then the first one added to the page will + get focus. + """ + + hint_content: Optional[Control] = None + """ + A placeholder `Control` for the dropdown's value that is displayed when `value` is + `None`. + """ + + select_icon: Optional[IconValueOrControl] = None + """ + The [name of the icon](https://flet.dev/docs/reference/icons) or `Control` to use + for the drop-down select button's icon. Defaults to an + `Icon(ft.Icons.ARROW_DROP_DOWN)`. + + Example with icon name: + ``` + icon=ft.Icons.BOOKMARK + ``` + Example with Control: + ``` + icon=ft.Icon(ft.Icons.BOOKMARK) + ``` + """ + + elevation: Number = 8 + """ + The dropdown's elevation. + + Defaults to `8`. + """ + + item_height: OptionalNumber = None + """ + The height of the items/options in the dropdown menu. + """ + + max_menu_height: OptionalNumber = None + """ + The maximum height of the dropdown menu. + """ + + select_icon_size: Number = 24.0 + """ + The size of the icon button which wraps `select_icon`. + + Defaults to `24.0`. + """ + + enable_feedback: Optional[bool] = None + """ + Whether detected gestures should provide acoustic and/or haptic feedback. On + Android, for example, setting this to `True` produce a click sound and a long-press + will produce a short vibration. + """ + + padding: OptionalPaddingValue = None + """ + The [padding](https://flet.dev/docs/reference/types/padding) around the visible + portion of this dropdown. + """ + + select_icon_enabled_color: OptionalColorValue = None + """ + The color of any `Icon` descendant of `select_icon` if this button is enabled. + """ + + select_icon_disabled_color: OptionalColorValue = None + """ + The color of any `Icon` descendant of `select_icon` if this button is disabled. + """ + + options_fill_horizontally: bool = True + """ + Whether the dropdown's inner contents to horizontally fill its parent. + By default this button's inner width is the minimum size of its content. + + If `True`, the inner width is expanded to fill its surrounding container. + + Value is of type `bool` and defaults to `True`. + """ + + disabled_hint_content: Optional[Control] = None + """ + A placeholder `Control` for the dropdown's value that is displayed when `value` is + `None` and the dropdown is disabled. + """ + + on_change: OptionalControlEventHandler["DropdownM2"] = None + """ + Fires when the selected item of this dropdown has changed. + """ + + on_focus: OptionalControlEventHandler["DropdownM2"] = None + """ + Fires when the control has received focus. + """ + + on_blur: OptionalControlEventHandler["DropdownM2"] = None + """ + Fires when the control has lost focus. + """ + + on_click: OptionalControlEventHandler["DropdownM2"] = None + """ + Fires when this dropdown is clicked. + """ + + def before_update(self): + super().before_update() + if ( + self.bgcolor is not None + or self.fill_color is not None + or self.focused_bgcolor is not None + ) and self.filled is None: + self.filled = True # required to display any of the above colors + + def __contains__(self, item): + return item in self.options diff --git a/sdk/python/packages/flet/src/flet/controls/material/elevated_button.py b/sdk/python/packages/flet/src/flet/controls/material/elevated_button.py new file mode 100644 index 000000000..4bb3788ef --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/elevated_button.py @@ -0,0 +1,156 @@ +import asyncio +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.buttons import ButtonStyle +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.types import ( + ClipBehavior, + IconValueOrControl, + Number, + OptionalColorValue, + StrOrControl, + UrlTarget, +) + +__all__ = ["ElevatedButton"] + + +@control("ElevatedButton") +class ElevatedButton(ConstrainedControl, AdaptiveControl): + """ + Elevated buttons are essentially filled tonal buttons with a shadow. To prevent + shadow creep, only use them when absolutely necessary, such as when the button + requires visual separation from a patterned background. + + Online docs: https://flet.dev/docs/controls/elevatedbutton + """ + + content: Optional[StrOrControl] = None + """ + A Control representing custom button content. + """ + + icon: Optional[IconValueOrControl] = None + """ + Icon shown in the button. + """ + + icon_color: OptionalColorValue = None + """ + Icon [color](https://flet.dev/docs/reference/colors). + """ + + color: OptionalColorValue = None + """ + Button's text [color](https://flet.dev/docs/reference/colors). If both `color` and + `style.color` are provided, `color` value will be used. + """ + + bgcolor: OptionalColorValue = None + """ + Button's background [color](https://flet.dev/docs/reference/colors). If both + `bgcolor` and `style.bgcolor` are provided, `bgcolor` value will be used. + """ + + elevation: Number = 1 + """ + Button's elevation. If both `elevation` and `style.elevation` are provided, + `elevation` value will be used. + """ + + style: Optional[ButtonStyle] = None + """ + The value is an instance of [`ButtonStyle`](https://flet.dev/docs/reference/types/buttonstyle) + class. + """ + + autofocus: Optional[bool] = None + """ + True if the control will be selected as the initial focus. If there is more than + one control on a page with autofocus set, then the first one added to the page will + get focus. + """ + + clip_behavior: Optional[ClipBehavior] = None + """ + The content will be clipped (or not) according to this option. + + Value is of type [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) + and defaults to `ClipBehavior.NONE`. + """ + + url: Optional[str] = None + """ + The URL to open when the button is clicked. If registered, `on_click` event is + fired after that. + """ + + url_target: Optional[UrlTarget] = None + """ + Where to open URL in the web mode. + + Value is of type [`UrlTarget`](https://flet.dev/docs/reference/types/urltarget) and + defaults to `UrlTarget.BLANK`. + """ + + on_click: OptionalControlEventHandler["ElevatedButton"] = None + """ + Fires when a user clicks the button. + """ + + on_long_press: OptionalControlEventHandler["ElevatedButton"] = None + """ + Fires when the button is long-pressed. + """ + + on_hover: OptionalControlEventHandler["ElevatedButton"] = None + """ + Fires when a mouse pointer enters or exists the button response area. `data` + property of event object contains `true` (string) when cursor enters and `false` + when it exits. + + ```python + import flet as ft + + def main(page: ft.Page): + def on_hover(e): + e.control.bgcolor = "orange" if e.data == "true" else "yellow" + e.control.update() + + page.add( + ft.ElevatedButton( + "I'm changing color on hover", bgcolor="yellow", on_hover=on_hover + ) + ) + + ft.run(main) + ``` + """ + + on_focus: OptionalControlEventHandler["ElevatedButton"] = None + """ + Fires when the control has received focus. + """ + + on_blur: OptionalControlEventHandler["ElevatedButton"] = None + """ + Fires when the control has lost focus. + """ + + def before_update(self): + super().before_update() + assert ( + self.icon + or isinstance(self.content, str) + or (isinstance(self.content, Control) and self.content.visible) + ), "at least icon or content (string or visible Control) must be provided" + + async def focus_async(self): + await self._invoke_method_async("focus") + + def focus(self): + asyncio.create_task(self.focus_async()) diff --git a/sdk/python/packages/flet/src/flet/controls/material/expansion_panel.py b/sdk/python/packages/flet/src/flet/controls/material/expansion_panel.py new file mode 100644 index 000000000..1ec6b83d5 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/expansion_panel.py @@ -0,0 +1,129 @@ +from dataclasses import field +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.padding import Padding, PaddingValue +from flet.controls.types import ( + Number, + OptionalColorValue, + OptionalNumber, +) + +__all__ = ["ExpansionPanel", "ExpansionPanelList"] + + +@control("ExpansionPanel") +class ExpansionPanel(ConstrainedControl, AdaptiveControl): + """ + A material expansion panel. It can either be expanded or collapsed. Its body is + only visible when it is expanded. + + Online docs: https://flet.dev/docs/controls/expansionpanel + """ + + header: Optional[Control] = None + """ + The control to be found in the header of the `ExpansionPanel`. If `can_tap_header` + is `True`, tapping on the header will expand or collapse the panel. + + If this property is `None`, the `ExpansionPanel` will have a placeholder `Text` as + header. + """ + + content: Optional[Control] = None + """ + The control to be found in the body of the `ExpansionPanel`. It is displayed below + the `header` when the panel is expanded. + + If this property is `None`, the `ExpansionPanel` will have a placeholder `Text` as + content. + """ + + bgcolor: OptionalColorValue = None + """ + The background [color](https://flet.dev/docs/reference/colors) of the panel. + """ + + expanded: bool = False + """ + Whether expanded(`True`) or collapsed(`False`). Defaults to `False`. + """ + + can_tap_header: bool = False + """ + If `True`, tapping on the panel's `header` will expand or collapse it. Defaults to + `False`. + """ + + splash_color: OptionalColorValue = None + """ + TBD + """ + + highlight_color: OptionalColorValue = None + """ + TBD + """ + + +@control("ExpansionPanelList") +class ExpansionPanelList(ConstrainedControl): + """ + A material expansion panel list that lays out its children and animates expansions. + + Online docs: https://flet.dev/docs/controls/expansionpanellist + """ + + controls: list[ExpansionPanel] = field(default_factory=list) + """ + A list of `ExpansionPanel`s to display inside `ExpansionPanelList`. + """ + + divider_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the divider when + `ExpansionPanel.expanded` is `False`. + """ + + elevation: Number = 2 + """ + Defines the elevation of the children controls (`ExpansionPanel`s), while it is + expanded. Default value is `2`. + """ + + expanded_header_padding: PaddingValue = field( + default_factory=lambda: Padding.symmetric(vertical=16.0) + ) + """ + Defines the padding around the header when expanded. + + Padding value is an instance of [`Padding`](https://flet.dev/docs/reference/types/padding) + class. Default value is `padding.symmetric(vertical=16.0)`. + """ + + expand_icon_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the icon. Defaults to + `colors.BLACK_54` in light theme mode and `colors.WHITE_60` in dark theme mode. + """ + + spacing: OptionalNumber = None + """ + The size of the gap between the `ExpansionPanel`s when expanded. + """ + + on_change: OptionalControlEventHandler["ExpansionPanelList"] = None + """ + Fires when an `ExpansionPanel` is expanded or collapsed. The event's data + (`e.data`), contains the index of the `ExpansionPanel` which triggered this event. + """ + + def before_update(self): + super().before_update() + assert self.elevation is None or self.elevation >= 0, ( + "elevation cannot be negative" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/material/expansion_tile.py b/sdk/python/packages/flet/src/flet/controls/material/expansion_tile.py new file mode 100644 index 000000000..2b102f625 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/expansion_tile.py @@ -0,0 +1,230 @@ +from enum import Enum +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.alignment import Alignment +from flet.controls.base_control import control +from flet.controls.buttons import OutlinedBorder +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.padding import OptionalPaddingValue +from flet.controls.types import ( + ClipBehavior, + CrossAxisAlignment, + IconValueOrControl, + OptionalColorValue, + OptionalNumber, + StrOrControl, + VisualDensity, +) + +__all__ = ["ExpansionTile", "TileAffinity"] + + +class TileAffinity(Enum): + LEADING = "leading" + TRAILING = "trailing" + PLATFORM = "platform" + + +@control("ExpansionTile") +class ExpansionTile(ConstrainedControl, AdaptiveControl): + """ + A single-line ListTile with an expansion arrow icon that expands or collapses the + tile to reveal or hide its controls. + + Online docs: https://flet.dev/docs/controls/expansiontile + """ + + title: StrOrControl + """ + A `Control` to display as primary content of the tile. + + Typically a [`Text`](https://flet.dev/docs/controls/text) control. + """ + + controls: Optional[list[Control]] = None + """ + The controls to be displayed when the tile expands. + + Typically a list of [`ListTile`](https://flet.dev/docs/controls/listtile) controls. + """ + + subtitle: Optional[StrOrControl] = None + """ + Additional content displayed below the title. + + Typically a [`Text`](https://flet.dev/docs/controls/text) control. + """ + + leading: Optional[IconValueOrControl] = None + """ + A `Control` to display before the title. + """ + + trailing: Optional[IconValueOrControl] = None + """ + A `Control` to display after the title. + + Typically an [`Icon`](https://flet.dev/docs/controls/icon) control. + """ + + controls_padding: OptionalPaddingValue = None + """ + Defines the padding around the `controls`. + + Padding value is an instance of [`Padding`](https://flet.dev/docs/reference/types/padding). + """ + + tile_padding: OptionalPaddingValue = None + """ + Defines the tile's padding. Default value is `padding.symmetric(horizontal=16.0)`. + + Padding value is an instance of [`Padding`](https://flet.dev/docs/reference/types/padding) + class. + """ + + affinity: Optional[TileAffinity] = None + """ + Typically used to force the expansion arrow icon to the tile's `leading` or + `trailing` edge. + + Value is of type [`TileAffinity`](https://flet.dev/docs/reference/types/tileaffinity) + and defaults to `TileAffinity.PLATFORM`. + """ + + expanded_alignment: Optional[Alignment] = None + """ + Defines the alignment of children, which are arranged in a column when the tile is + expanded. + + Value is of type [`Alignment`](https://flet.dev/docs/reference/types/alignment). + """ + + expanded_cross_axis_alignment: CrossAxisAlignment = CrossAxisAlignment.CENTER + """ + Defines the alignment of each child control within `controls` when the tile is + expanded. + + Value is of type [`CrossAxisAlignment`](https://flet.dev/docs/reference/types/crossaxisalignment) + and defaults to `CrossAxisAlignment.CENTER`. + """ + + clip_behavior: Optional[ClipBehavior] = None + """ + The content will be clipped (or not) according to this option. + + Value is of type [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) + and defaults to `ClipBehavior.NONE`. + """ + + initially_expanded: bool = False + """ + A boolean value which defines whether the tile is initially expanded or collapsed. + + Defaults to `False`. + """ + + maintain_state: bool = False + """ + A boolean value which defines whether the state of the `controls` is maintained + when the tile expands and collapses. + + Defaults to `False`. + """ + + text_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the tile's titles when the + sublist is expanded. + """ + + icon_color: OptionalColorValue = None + """ + The icon [color](https://flet.dev/docs/reference/colors) of tile's expansion arrow + icon when the sublist is expanded. + """ + + shape: Optional[OutlinedBorder] = None + """ + The tile's border shape when the sublist is expanded. + + Value is of type [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder). + """ + + bgcolor: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to display behind the sublist + when expanded. + """ + + collapsed_bgcolor: OptionalColorValue = None + """ + Defines the background [color](https://flet.dev/docs/reference/colors) of tile when + the sublist is collapsed. + """ + + collapsed_icon_color: OptionalColorValue = None + """ + The icon [color](https://flet.dev/docs/reference/colors) of tile's expansion arrow + icon when the sublist is collapsed. + """ + + collapsed_text_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the tile's titles when the + sublist is collapsed. + """ + + collapsed_shape: Optional[OutlinedBorder] = None + """ + The tile's border shape when the sublist is collapsed. The value is an instance + of [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder). + """ + + dense: Optional[bool] = None + """ + Whether this list tile is part of a vertically dense list. Dense list tiles default + to a smaller height. + + It is not recommended to set this property to `True` when in Material3. + """ + + enable_feedback: bool = True + """ + Whether detected gestures should provide acoustic and/or haptic feedback. For + example, on Android a tap will produce a clicking sound and a long-press will + produce a short vibration, when feedback is enabled. + + Defaults to `True`. + """ + + show_trailing_icon: bool = True + """ + Whether to show the trailing icon (be it the default icon or the custom `trailing`, + if specified and visible). + + Defaults to `True`. + """ + + min_tile_height: OptionalNumber = None + """ + The minimum height of the tile. + """ + + visual_density: Optional[VisualDensity] = None + """ + Defines how compact the control's layout will be. + + Value is of type [`VisualDensity`](https://flet.dev/docs/reference/types/visualdensity). + """ + + on_change: OptionalControlEventHandler["ExpansionTile"] = None + """ + Fires when a user clicks or taps the list tile. + """ + + def before_update(self): + super().before_update() + assert self.title.visible, "title must be visible" diff --git a/sdk/python/packages/flet/src/flet/controls/material/filled_button.py b/sdk/python/packages/flet/src/flet/controls/material/filled_button.py new file mode 100644 index 000000000..c24038886 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/filled_button.py @@ -0,0 +1,13 @@ +from flet.controls.base_control import control +from flet.controls.material.elevated_button import ElevatedButton + + +@control("FilledButton") +class FilledButton(ElevatedButton): + """ + Filled buttons have the most visual impact after the FloatingActionButton (https://flet.dev/docs/controls/floatingactionbutton), + and should be used for important, final actions that complete a flow, like Save, + Join now, or Confirm. + + Online docs: https://flet.dev/docs/controls/filledbutton + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/filled_tonal_button.py b/sdk/python/packages/flet/src/flet/controls/material/filled_tonal_button.py new file mode 100644 index 000000000..c3448f567 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/filled_tonal_button.py @@ -0,0 +1,14 @@ +from flet.controls.base_control import control +from flet.controls.material.elevated_button import ElevatedButton + + +@control("FilledTonalButton") +class FilledTonalButton(ElevatedButton): + """ + A filled tonal button is an alternative middle ground between FilledButton and + OutlinedButton buttons. They’re useful in contexts where a lower-priority button + requires slightly more emphasis than an outline would give, such as "Next" in an + onboarding flow. Tonal buttons use the secondary color mapping. + + Online docs: https://flet.dev/docs/controls/filledtonalbutton + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/floating_action_button.py b/sdk/python/packages/flet/src/flet/controls/material/floating_action_button.py new file mode 100644 index 000000000..7bc6fb365 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/floating_action_button.py @@ -0,0 +1,181 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.buttons import OutlinedBorder +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.types import ( + ClipBehavior, + IconValueOrControl, + MouseCursor, + OptionalColorValue, + OptionalNumber, + StrOrControl, + UrlTarget, +) + +__all__ = ["FloatingActionButton"] + + +@control("FloatingActionButton") +class FloatingActionButton(ConstrainedControl): + """ + A floating action button is a circular icon button that hovers over content to + promote a primary action in the application. Floating action button is usually set + to `page.floating_action_button`, but can also be added as a regular control at any + place on a page. + + Online docs: https://flet.dev/docs/controls/floatingactionbutton + """ + + content: Optional[StrOrControl] = None + """ + A Control representing custom button content. + """ + + icon: Optional[IconValueOrControl] = None + """ + Icon shown in the button. + """ + + bgcolor: OptionalColorValue = None + """ + Button background [color](https://flet.dev/docs/reference/colors). + """ + + shape: Optional[OutlinedBorder] = None + """ + The shape of the FAB's border. + + The value is an instance of [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder) + class. + """ + + autofocus: bool = False + """ + True if the control will be selected as the initial focus. If there is more than + one control on a page with autofocus set, then the first one added to the page will + get focus. + """ + + mini: bool = False + """ + Controls the size of this button. + + By default, floating action buttons are non-mini and have a height and width of + `56.0` logical pixels. Mini floating action buttons have a height and width of + `40.0` logical pixels with a layout width and height of `48.0` logical pixels. + """ + + foreground_color: OptionalColorValue = None + """ + The default foreground [color](https://flet.dev/docs/reference/colors) for icons + and text within the button. + """ + + focus_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use for filling the button + when the button has input focus. + """ + + clip_behavior: ClipBehavior = ClipBehavior.NONE + """ + The content will be clipped (or not) according to this option. + + Value is of type [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) + and defaults to `ClipBehavior.NONE`. + """ + + elevation: OptionalNumber = None + """ + The button's elevation. + + Defaults to `6`. + """ + + disabled_elevation: OptionalNumber = None + """ + The button's elevation when disabled. + + Defaults to the same value as `elevation`. + """ + + focus_elevation: OptionalNumber = None + """ + The button's elevation when it has input focus. + + Defaults to `8`. + """ + + highlight_elevation: OptionalNumber = None + """ + The button's elevation when being touched. + + Defaults to `12`. + """ + + hover_elevation: OptionalNumber = None + """ + The button's elevation when it is enabled and being hovered. + + Defaults to `8`. + """ + + enable_feedback: Optional[bool] = None + """ + Whether detected gestures should provide acoustic and/or haptic feedback. On + Android, for example, setting this to `True` will produce a click sound and a + long-press will produce a short vibration. + """ + + url: Optional[str] = None + """ + The URL to open when the button is clicked. If registered, `on_click` event is + fired after that. + """ + + url_target: Optional[UrlTarget] = None + """ + Where to open URL in the web mode. + + Value is of type [`UrlTarget`](https://flet.dev/docs/reference/types/urltarget) and + defaults to `UrlTarget.BLANK`. + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + The cursor to be displayed when a mouse pointer enters or is hovering over this + control. + + Value is of type [`MouseCursor`](https://flet.dev/docs/reference/types/mousecursor). + """ + + on_click: OptionalControlEventHandler["FloatingActionButton"] = None + """ + Fires when a user clicks the button. + """ + + def before_update(self): + super().before_update() + assert ( + self.icon + or isinstance(self.content, str) + or (isinstance(self.content, Control) and self.content.visible) + ), "at minimum, icon or a visible content must be provided" + assert self.elevation is None or self.elevation >= 0, ( + "elevation cannot be negative" + ) + assert self.disabled_elevation is None or self.disabled_elevation >= 0, ( + "disabled_elevation cannot be negative" + ) + assert self.focus_elevation is None or self.focus_elevation >= 0, ( + "focus_elevation cannot be negative" + ) + assert self.highlight_elevation is None or self.highlight_elevation >= 0, ( + "highlight_elevation cannot be negative" + ) + assert self.hover_elevation is None or self.hover_elevation >= 0, ( + "hover_elevation cannot be negative" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/material/form_field_control.py b/sdk/python/packages/flet/src/flet/controls/material/form_field_control.py new file mode 100644 index 000000000..59b283297 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/form_field_control.py @@ -0,0 +1,336 @@ +import asyncio +from dataclasses import field +from enum import Enum +from typing import Optional, Union + +from flet.controls.base_control import control +from flet.controls.border_radius import OptionalBorderRadiusValue +from flet.controls.box import BoxConstraints +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.duration import OptionalDurationValue +from flet.controls.padding import OptionalPaddingValue +from flet.controls.text_style import TextStyle +from flet.controls.types import ( + IconValueOrControl, + OptionalColorValue, + OptionalNumber, + OptionalString, + StrOrControl, + VerticalAlignment, +) + + +class InputBorder(Enum): + NONE = "none" + OUTLINE = "outline" + UNDERLINE = "underline" + + +@control(kw_only=True) +class FormFieldControl(ConstrainedControl): + text_size: OptionalNumber = None + """ + Text size in virtual pixels. + """ + + text_style: TextStyle = field(default_factory=lambda: TextStyle()) + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) to use for the + text being edited. + """ + + text_vertical_align: Union[VerticalAlignment, OptionalNumber] = None + """ + Defines how the text should be aligned vertically. + + Value can either be a number ranging from `-1.0` (topmost location) to `1.0` + (bottommost location) or of type [`VerticalAlignment`](https://flet.dev/docs/reference/types/verticalalignment). + Defaults to `VerticalAlignment.CENTER`. + """ + + label: Optional[StrOrControl] = None + """ + Optional text that describes the input field. + + When the input field is empty and unfocused, the label is displayed on top of the + input field (i.e., at the same location on the screen where text may be entered in + the input field). When the input field receives focus (or if the field is + non-empty) the label moves above, either vertically adjacent to, or to the center + of the input field. + """ + + label_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) to use for + `label`. + """ + + icon: Optional[IconValueOrControl] = None + """ + The icon to show before the input field and outside of the decoration's container. + Can be a `Control` or an icon name. + """ + + border: InputBorder = InputBorder.OUTLINE + """ + Border around input. + + Value is of type [`InputBorder`](https://flet.dev/docs/reference/types/inputborder) + and defaults to `InputBorder.OUTLINE`. + """ + + color: OptionalColorValue = None + """ + Text [color](https://flet.dev/docs/reference/colors). + """ + + bgcolor: OptionalColorValue = None + """ + TextField background [color](https://flet.dev/docs/reference/colors). Will not be + visible if `filled=False`. + """ + + border_radius: OptionalBorderRadiusValue = None + """ + Border radius is an instance of [`BorderRadius`](https://flet.dev/docs/reference/types/borderradius) + class. + """ + + border_width: OptionalNumber = None + """ + The width of the border in virtual pixels. Set to `0` to completely remove the + border. + + Defaults to `1`. + """ + + border_color: OptionalColorValue = None + """ + Border [color](https://flet.dev/docs/reference/colors). Could be `transparent` to + hide the border. + """ + + focused_color: OptionalColorValue = None + """ + Text [color](https://flet.dev/docs/reference/colors) when TextField is focused. + """ + + focused_bgcolor: OptionalColorValue = None + """ + Background [color](https://flet.dev/docs/reference/colors) of TextField in focused + state. Will not be visible if `filled=False`. + """ + + focused_border_width: OptionalNumber = None + """ + Border width in focused state. + """ + + focused_border_color: OptionalColorValue = None + """ + Border [color](https://flet.dev/docs/reference/colors) in focused state. + """ + + content_padding: OptionalPaddingValue = None + """ + The padding for the input decoration's container. + + The value is an instance of [`padding.Padding`](https://flet.dev/docs/reference/types/padding) + class or a number. + """ + + dense: Optional[bool] = None + """ + Whether the TextField is part of a dense form (ie, uses less vertical space). + """ + + filled: Optional[bool] = None + """ + If `True` the decoration's container is filled with theme `fill_color`. + + If `filled=None`(default), then it is implicitly set to `True` when at least one of + the following is not `None`: `fill_color`, `focused_bgcolor`, `hover_color` and + `bgcolor`. + """ + + fill_color: OptionalColorValue = None + """ + Background [color](https://flet.dev/docs/reference/colors) of TextField. Will not + be visible if `filled=False`. + """ + + focus_color: OptionalColorValue = None + """ + TBD + """ + + align_label_with_hint: Optional[bool] = None + """ + TBD + """ + + hover_color: OptionalColorValue = None + """ + Background [color](https://flet.dev/docs/reference/colors) of TextField when + hovered. Will not be visible if `filled=False`. + """ + + hint_text: OptionalString = None + """ + Text that suggests what sort of input the field accepts. + + Displayed on top of the input when the it's empty and either (a) `label` is null or + (b) the input has the focus. + """ + + hint_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) to use for + `hint_text`. + """ + + hint_fade_duration: OptionalDurationValue = None + """ + TBD + """ + + hint_max_lines: Optional[int] = None + """ + TBD + """ + + helper: Optional[StrOrControl] = None + """ + Text that provides context about the input's value, such as how the value will be + used. + + If non-null, the text is displayed below the input decorator, in the same location + as `error_text`. If a non-null `error_text` value is specified then the helper text + is not shown. + """ + + helper_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) to use for + `helper_text`. + """ + + helper_max_lines: Optional[int] = None + """ + TBD + """ + + counter: Optional[StrOrControl] = None + """ + A `Control` to place below the line as a character count. + + If `None` or an empty string and `counter_text` isn't specified, then nothing will + appear in the counter's location. + """ + + counter_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) to use for + `counter_text`. + """ + + error: Optional[StrOrControl] = None + """ + Text that appears below the input border. + + If non-null, the border's color animates to red and the `helper_text` is not shown. + """ + + error_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) to use for + `error_text`. + """ + + error_max_lines: Optional[int] = None + """ + TBD + """ + + prefix: Optional[StrOrControl] = None + """ + Optional `Control` to place on the line before the input. + + This can be used, for example, to add some padding to text that would otherwise be + specified using `prefix_text`, or to add a custom control in front of the input. + The control's baseline is lined up with the input baseline. + + Only one of `prefix` and `prefix_text` can be specified. + + The `prefix` appears after the `prefix_icon`, if both are specified. + """ + + prefix_icon: Optional[IconValueOrControl] = None + """ + An icon that appears before the `prefix` or `prefix_text` and before the editable + part of the text field, within the decoration's container. Can be a `Control` or an + icon name. + """ + + prefix_icon_size_constraints: Optional[BoxConstraints] = None + """ + TBD + """ + + prefix_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) to use for + `prefix_text`. + """ + + suffix: Optional[StrOrControl] = None + """ + Optional `Control` to place on the line after the input. + + This can be used, for example, to add some padding to the text that would otherwise + be specified using `suffix_text`, or to add a custom control after the input. + The control's baseline is lined up with the input baseline. + + Only one of `suffix` and `suffix_text` can be specified. + + The `suffix` appears before the `suffix_icon`, if both are specified. + """ + + suffix_icon: Optional[IconValueOrControl] = None + """ + An icon that appears after the editable part of the text field and after the + `suffix` or `suffix_text`, within the decoration's container. Can be a `Control` or + an icon name. + """ + + suffix_icon_size_constraints: Optional[BoxConstraints] = None + """ + TBD + """ + + size_constraints: Optional[BoxConstraints] = None + """ + TBD + """ + + collapsed: Optional[bool] = None + """ + TBD + """ + + fit_parent_size: Optional[bool] = None + """ + TBD + """ + + suffix_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) to use for + `suffix_text`. + """ + + async def focus_async(self): + await self._invoke_method_async("focus") + + def focus(self): + asyncio.create_task(self.focus_async()) diff --git a/sdk/python/packages/flet/src/flet/controls/material/icon_button.py b/sdk/python/packages/flet/src/flet/controls/material/icon_button.py new file mode 100644 index 000000000..acdb8aea7 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/icon_button.py @@ -0,0 +1,250 @@ +import asyncio +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.alignment import Alignment +from flet.controls.base_control import control +from flet.controls.box import BoxConstraints +from flet.controls.buttons import ButtonStyle +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.padding import OptionalPaddingValue +from flet.controls.types import ( + IconValueOrControl, + MouseCursor, + OptionalColorValue, + OptionalNumber, + UrlTarget, + VisualDensity, +) +from flet.utils.deprecated import deprecated_warning + +__all__ = ["IconButton"] + + +@control("IconButton") +class IconButton(ConstrainedControl, AdaptiveControl): + """ + An icon button is a round button with an icon in the middle that reacts to touches + by filling with color (ink). + + Icon buttons are commonly used in the toolbars, but they can be used in many other + places as well. + + Online docs: https://flet.dev/docs/controls/iconbutton + """ + + def __setattr__(self, name, value): + if name == "content" and value is not None: + deprecated_warning( + name="content", + reason="Use 'icon' instead.", + version="0.70.0", + delete_version="0.73.0", + ) + super().__setattr__(name, value) + + icon: Optional[IconValueOrControl] = None + """ + Icon shown in the button. + """ + + icon_color: OptionalColorValue = None + """ + Icon [color](https://flet.dev/docs/reference/colors). + """ + + icon_size: OptionalNumber = None + """ + Icon size in virtual pixels. + + Defaults to `24`. + """ + + selected: Optional[bool] = None + """ + The optional selection state of the icon button. + + If this property is not set, the button will behave as a normal push button, + otherwise, the button will toggle between showing `icon` and `selected_icon` based + on the value of `selected`. + + If True, it will show `selected_icon`, if False it will show `icon`. + """ + + selected_icon: Optional[IconValueOrControl] = None + """ + Icon shown in the button in selected state. + """ + + selected_icon_color: OptionalColorValue = None + """ + Icon [color](https://flet.dev/docs/reference/colors) for the selected state. + + An example of icon toggle button: + + + + ```python + import flet as ft + + def main(page: ft.Page): + + def toggle_icon_button(e): + e.control.selected = not e.control.selected + + page.add( + ft.IconButton( + icon=ft.Icons.BATTERY_1_BAR, + selected_icon=ft.Icons.BATTERY_FULL, + on_click=toggle_icon_button, + selected=False, + style=ft.ButtonStyle( + color={"selected": ft.Colors.GREEN, "": ft.Colors.RED}, + ), + ) + ) + + ft.app(main) + ``` + """ + + bgcolor: OptionalColorValue = None + """ + TBD + """ + + highlight_color: OptionalColorValue = None + """ + The button's [color](https://flet.dev/docs/reference/colors) when the button is + pressed. The highlight fades in quickly as the button is held down. + """ + + style: Optional[ButtonStyle] = None + """ + Value is of type [`ButtonStyle`](https://flet.dev/docs/reference/types/buttonstyle). + """ + + autofocus: bool = False + """ + True if the control will be selected as the initial focus. If there is more than + one control on a page with autofocus set, then the first one added to the page will + get focus. + """ + + disabled_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use for the icon inside the + button when disabled. + """ + + hover_color: OptionalColorValue = None + """ + The button's [color](https://flet.dev/docs/reference/colors) when hovered. + """ + + focus_color: OptionalColorValue = None + """ + The button's [color](https://flet.dev/docs/reference/colors) when in focus. + """ + + splash_color: OptionalColorValue = None + """ + The primary [color](https://flet.dev/docs/reference/colors) of the button when the + button is in the down (pressed) state. + """ + + splash_radius: OptionalNumber = None + """ + The splash radius. Honoured only when in Material 2. + """ + + alignment: Optional[Alignment] = None + """ + Defines how the icon is positioned within the IconButton. Alignment is an instance + of [`Alignment`](https://flet.dev/docs/reference/types/alignment) class. + + Defaults to `alignment.center`. + """ + + padding: OptionalPaddingValue = None + """ + Defines the padding around this button. The entire padded icon will react to input + gestures. + + Value is of type [`Padding`](https://flet.dev/docs/reference/types/padding) and + defaults to `Padding.all(8)`. + """ + + enable_feedback: Optional[bool] = None + """ + Whether detected gestures should provide acoustic and/or haptic feedback. + On Android, for example, setting this to `True` produce a click sound and a + long-press will produce a short vibration. + """ + + url: Optional[str] = None + """ + The URL to open when the button is clicked. If registered, `on_click` event is fired + after that. + """ + + url_target: Optional[UrlTarget] = None + """ + Where to open URL in the web mode. + + Value is of type [`UrlTarget`](https://flet.dev/docs/reference/types/urltarget). + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + The cursor to be displayed when a mouse pointer enters or is hovering over this + control. + + Value is of type [`MouseCursor`](https://flet.dev/docs/reference/types/mousecursor). + """ + + visual_density: Optional[VisualDensity] = None + """ + Defines how compact the control's layout will be. + + Value is of type [`VisualDensity`](https://flet.dev/docs/reference/types/visualdensity). + """ + + size_constraints: Optional[BoxConstraints] = None + """ + TBD + """ + + on_click: OptionalControlEventHandler["IconButton"] = None + """ + Fires when a user clicks the button. + """ + + on_focus: OptionalControlEventHandler["IconButton"] = None + """ + Fires when the control has received focus. + """ + + on_blur: OptionalControlEventHandler["IconButton"] = None + """ + Fires when the control has lost focus. + """ + + content: Optional[Control] = None # todo(0.70.3): remove in favor of icon + """ + A Control representing custom button content. + """ + + async def focus_async(self): + """ + Moves focus to a button. + """ + await self._invoke_method_async("focus") + + def focus(self): + """ + Moves focus to a button. + """ + asyncio.create_task(self.focus_async()) diff --git a/sdk/python/packages/flet/src/flet/core/icons.py b/sdk/python/packages/flet/src/flet/controls/material/icons.py similarity index 99% rename from sdk/python/packages/flet/src/flet/core/icons.py rename to sdk/python/packages/flet/src/flet/controls/material/icons.py index 11cd51464..4c120c267 100644 --- a/sdk/python/packages/flet/src/flet/core/icons.py +++ b/sdk/python/packages/flet/src/flet/controls/material/icons.py @@ -8,21 +8,24 @@ import random from enum import Enum -from typing import Dict, List, Optional +from typing import Optional + +__all__ = ["Icons"] class Icons(str, Enum): @staticmethod def random( - exclude: Optional[List["Icons"]] = None, - weights: Optional[Dict["Icons", int]] = None, + exclude: Optional[list["Icons"]] = None, + weights: Optional[dict["Icons", int]] = None, ) -> Optional["Icons"]: """ Selects a random icon, with optional exclusions and weights. Args: exclude: A list of icons members to exclude from the selection. - weights: A dictionary mapping icon members to their respective weights for weighted random selection. + weights: A dictionary mapping icon members to their respective weights for + weighted random selection. Returns: A randomly selected icon, or None if all members are excluded. diff --git a/sdk/python/packages/flet/src/flet/controls/material/list_tile.py b/sdk/python/packages/flet/src/flet/controls/material/list_tile.py new file mode 100644 index 000000000..552b02cb8 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/list_tile.py @@ -0,0 +1,288 @@ +from enum import Enum +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.buttons import OutlinedBorder +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.padding import OptionalPaddingValue +from flet.controls.text_style import TextStyle +from flet.controls.types import ( + IconValueOrControl, + MouseCursor, + Number, + OptionalColorValue, + OptionalNumber, + StrOrControl, + UrlTarget, + VisualDensity, +) + +__all__ = ["ListTile", "ListTileTitleAlignment", "ListTileStyle"] + + +class ListTileTitleAlignment(Enum): + TOP = "top" + CENTER = "center" + BOTTOM = "bottom" + THREE_LINE = "threeLine" + TITLE_HEIGHT = "titleHeight" + + +class ListTileStyle(Enum): + LIST = "list" + DRAWER = "drawer" + + +@control("ListTile") +class ListTile(ConstrainedControl, AdaptiveControl): + """ + A single fixed-height row that typically contains some text as well as a leading or + trailing icon. + + Online docs: https://flet.dev/docs/controls/listtile + """ + + title: Optional[StrOrControl] = None + """ + A `Control` to display as primary content of the list tile. + + Typically a [`Text`](https://flet.dev/docs/controls/text) control. This should not + wrap. To enforce the single line limit, use [`Text.max_lines`](https://flet.dev/docs/controls/text#max_lines). + """ + + subtitle: Optional[StrOrControl] = None + """ + Additional content displayed below the title. Typically a + [`Text`](https://flet.dev/docs/controls/text) widget. + + If `is_three_line` is `False`, this should not wrap. If `is_three_line` is `True`, + this should be configured to take a maximum of two lines. For example, you can use + [`Text.max_lines`](https://flet.dev/docs/controls/text#max_lines) to enforce the + number of lines. + """ + + is_three_line: bool = False + """ + Whether this list tile is intended to display three lines of text. + + If `True`, then subtitle must be non-null (since it is expected to give the second + and third lines of text). + + If `False`, the list tile is treated as having one line if the subtitle is null and + treated as having two lines if the subtitle is non-null. + + When using a Text control for title and subtitle, you can enforce line limits + using [`Text.max_lines`](https://flet.dev/docs/controls/text#max_lines). + """ + + leading: Optional[IconValueOrControl] = None + """ + A `Control` to display before the title. + """ + + trailing: Optional[IconValueOrControl] = None + """ + A `Control` to display after the title. Typically an [`Icon`](https://flet.dev/docs/controls/icon) + control. + """ + + content_padding: OptionalPaddingValue = None + """ + The tile's internal padding. Insets a ListTile's contents: its `leading`, `title`, + `subtitle`, and `trailing` controls. + + Value is of type [`Padding`](https://flet.dev/docs/reference/types/padding) and + defaults to `padding.symmetric(horizontal=16)`. + """ + + bgcolor: OptionalColorValue = None + """ + The list tile's background [color](https://flet.dev/docs/reference/colors). + """ + + bgcolor_activated: Optional[str] = None + """ + The list tile's splash [color](https://flet.dev/docs/reference/colors) after the + tile was tapped. + """ + + hover_color: OptionalColorValue = None + """ + The tile's [color](https://flet.dev/docs/reference/colors) when hovered. + """ + + selected: bool = False + """ + If this tile is also enabled then icons and text are rendered with the same color. + By default the selected color is the theme's primary color. + """ + + dense: bool = False + """ + Whether this list tile is part of a vertically dense list. Dense list tiles default + to a smaller height. + """ + + autofocus: bool = False + """ + `True` if the control will be selected as the initial focus. If there is more than + one control on a page with autofocus set, then the first one added to the page will + get focus. + """ + + toggle_inputs: bool = False + """ + Whether clicking on a list tile should toggle the state of `Radio`, `Checkbox` or ` + Switch` inside the tile. + + Defaults to `False`. + """ + + selected_color: OptionalColorValue = None + """ + Defines the [color](https://flet.dev/docs/reference/colors) used for icons and text + when `selected=True`. + """ + + selected_tile_color: OptionalColorValue = None + """ + Defines the background [color](https://flet.dev/docs/reference/colors) of ListTile + when `selected=True`. + """ + + style: Optional[ListTileStyle] = None + """ + Defines the font used for the title. + + Value is of type [`ListTileStyle`](https://flet.dev/docs/reference/types/listtilestyle) + and defaults to `ListTileStyle.LIST`. + """ + + enable_feedback: bool = True + """ + Whether detected gestures should provide acoustic and/or haptic feedback. + On Android, for example, setting this to `True` produce a click sound and a + long-press will produce a short vibration. + + Defaults to `True`. + """ + + horizontal_spacing: Number = 16.0 + """ + The horizontal gap between the `title` and the `leading`/`trailing` controls. + + Defaults to `16`. + """ + + min_leading_width: Number = 40.0 + """ + The minimum width allocated for the `leading` control. + + Defaults to `40`. + """ + + min_vertical_padding: Number = 4.0 + """ + The minimum padding on the top and bottom of the `title` and `subtitle` controls. + + Defaults to `4`. + """ + + url: Optional[str] = None + """ + The URL to open when the list tile is clicked. If registered, `on_click` event is + fired after that. + """ + + url_target: Optional[UrlTarget] = None + """ + Where to open URL in the web mode. + + Value is of type [`UrlTarget`](https://flet.dev/docs/reference/types/urltarget). + """ + + title_alignment: Optional[ListTileTitleAlignment] = None + """ + Defines how `leading` and `trailing` are vertically aligned relative to the titles + (`title` and `subtitle`). + + Value is of type [`ListTileAlignment`](https://flet.dev/docs/reference/types/listtilealignment) + and defaults to `ListTileAlignment.THREE_LINE` in Material 3 or + `ListTileAlignment.TITLE_HEIGHT` in Material 2. + """ + + icon_color: OptionalColorValue = None + """ + Defines the default [color](https://flet.dev/docs/reference/colors) for the `Icon`s + present in `leading` and `trailing`. + """ + + text_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used for text. Defines the color + of `Text` controls found in `title`, `subtitle`, `leading`, and `trailing`. + """ + + shape: Optional[OutlinedBorder] = None + """ + The tile's shape. The value is an instance of [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder) + class. + """ + + visual_density: Optional[VisualDensity] = None + """ + Defines how compact the control's layout will be. + + Value is of type [`VisualDensity`](https://flet.dev/docs/reference/types/visualdensity). + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + The cursor to be displayed when a mouse pointer enters or is hovering over this + control. The value is [`MouseCursor`](https://flet.dev/docs/reference/types/mousecursor) + enum. + """ + + title_text_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) for the `title` + control. + """ + + subtitle_text_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) for the + `subtitle` control. + """ + + leading_and_trailing_text_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) for the + `leading` and `trailing` controls. + """ + + min_height: OptionalNumber = None + """ + The minimum height allocated for this control. + + If `None` or not set, default tile heights are `56.0`, `72.0`, and `88.0` for one, + two, and three lines of text respectively. + If [`dense`](https://flet.dev/docs/controls/listtile#dense) is `True`, these + defaults are changed to `48.0`, `64.0`, and `76.0`. + + Note that, a visual density value or a large title will also adjust the default + tile heights. + """ + + on_click: OptionalControlEventHandler["ListTile"] = None + """ + Fires when a user clicks or taps the list tile. + """ + + on_long_press: OptionalControlEventHandler["ListTile"] = None + """ + Fires when the user long-presses on this list tile. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/menu_bar.py b/sdk/python/packages/flet/src/flet/controls/material/menu_bar.py new file mode 100644 index 000000000..09273b93d --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/menu_bar.py @@ -0,0 +1,63 @@ +from dataclasses import dataclass, field +from typing import Optional + +from flet.controls.alignment import Alignment +from flet.controls.base_control import control +from flet.controls.border import BorderSide +from flet.controls.buttons import OutlinedBorder +from flet.controls.control import Control +from flet.controls.control_state import ControlStateValue +from flet.controls.padding import PaddingValue +from flet.controls.types import ClipBehavior, ColorValue, MouseCursor, OptionalNumber + +__all__ = ["MenuBar", "MenuStyle"] + + +@dataclass +class MenuStyle: + alignment: Optional[Alignment] = None + bgcolor: Optional[ControlStateValue[ColorValue]] = None + shadow_color: Optional[ControlStateValue[ColorValue]] = None + surface_tint_color: Optional[ControlStateValue[ColorValue]] = None + elevation: Optional[ControlStateValue[OptionalNumber]] = None + padding: Optional[ControlStateValue[PaddingValue]] = None + side: Optional[ControlStateValue[BorderSide]] = None + shape: Optional[ControlStateValue[OutlinedBorder]] = None + mouse_cursor: Optional[ControlStateValue[MouseCursor]] = None + + +@control("MenuBar") +class MenuBar(Control): + """ + A menu bar that manages cascading child menus. + + It could be placed anywhere but typically resides above the main body of the + application and defines a menu system for invoking callbacks in response to user + selection of a menu item. + + Online docs: https://flet.dev/docs/controls/menubar + """ + + controls: list[Control] = field(default_factory=list) + """ + The list of menu items that are the top level children of the `MenuBar`. + """ + + clip_behavior: ClipBehavior = ClipBehavior.NONE + """ + Whether to clip the content of this control or not. + + Value is of type [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) + and defaults to `ClipBehavior.NONE`. + """ + + style: Optional[MenuStyle] = None + """ + Value is of type [`MenuStyle`](https://flet.dev/docs/reference/types/menustyle). + """ + + def before_update(self): + super().before_update() + assert any(c.visible for c in self.controls), ( + "MenuBar must have at minimum one visible control" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/material/menu_item_button.py b/sdk/python/packages/flet/src/flet/controls/material/menu_item_button.py new file mode 100644 index 000000000..dd598cfc0 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/menu_item_button.py @@ -0,0 +1,112 @@ +from typing import Optional + +from flet.controls.alignment import Axis +from flet.controls.base_control import control +from flet.controls.buttons import ButtonStyle +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.types import ClipBehavior, StrOrControl + +__all__ = ["MenuItemButton"] + + +@control("MenuItemButton") +class MenuItemButton(ConstrainedControl): + """ + A button for use in a MenuBar or on its own, that can be activated by click or + keyboard navigation. + + Online docs: https://flet.dev/docs/controls/menuitembutton + """ + + content: Optional[StrOrControl] = None + """ + The child control or text to be displayed in the center of this button. + + Typically this is the button's label, using a `Text` control. + """ + + close_on_click: bool = True + """ + Defines if the menu will be closed when the `MenuItemButton` is clicked. + + Defaults to `True`. + """ + + focus_on_hover: bool = True + """ + Determine if hovering can request focus. + + Defaults to `True`. + """ + + leading: Optional[Control] = None + """ + An optional control to display before the `content`. + + Typically an [`Icon`](https://flet.dev/docs/controls/icon) control. + """ + + trailing: Optional[Control] = None + """ + An optional control to display after the `content`. + + Typically an [`Icon`](https://flet.dev/docs/controls/icon) control. + """ + + clip_behavior: ClipBehavior = ClipBehavior.NONE + """ + Whether to clip the content of this control or not. + + Value is of type [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) + and defaults to `ClipBehavior.NONE`. + """ + + style: Optional[ButtonStyle] = None + """ + Customizes this button's appearance. + + Value is of type [`ButtonStyle`](https://flet.dev/docs/reference/types/buttonstyle). + """ + + semantic_label: Optional[str] = None + """ + A string that describes the button's action to assistive technologies. + """ + + autofocus: bool = False + """ + Whether this button should automatically request focus. + + Defaults to `False`. + """ + + overflow_axis: Axis = Axis.HORIZONTAL + """ + The direction in which the menu item expands. + + If the menu item button is a descendent of `MenuBar`, then this property is ignored. + + Value is of type [`Axis`](https://flet.dev/docs/reference/types/axis). + """ + + on_click: OptionalControlEventHandler["MenuItemButton"] = None + """ + Fired when the button is clicked. + """ + + on_hover: OptionalControlEventHandler["MenuItemButton"] = None + """ + Fired when the button is hovered. + """ + + on_focus: OptionalControlEventHandler["MenuItemButton"] = None + """ + Fired when the button receives focus. + """ + + on_blur: OptionalControlEventHandler["MenuItemButton"] = None + """ + Fired when this button loses focus. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/navigation_bar.py b/sdk/python/packages/flet/src/flet/controls/material/navigation_bar.py new file mode 100644 index 000000000..56080deb3 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/navigation_bar.py @@ -0,0 +1,187 @@ +from dataclasses import field +from enum import Enum +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.border import Border +from flet.controls.buttons import OutlinedBorder +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.control_state import ControlStateValue +from flet.controls.duration import OptionalDurationValue +from flet.controls.types import ( + ColorValue, + IconValueOrControl, + OptionalColorValue, + OptionalNumber, +) + +__all__ = ["NavigationBar", "NavigationBarDestination", "NavigationBarLabelBehavior"] + + +class NavigationBarLabelBehavior(Enum): + """ + Defines how the destinations' labels will be laid out and when they'll + be displayed. + """ + + ALWAYS_SHOW = "alwaysShow" + ALWAYS_HIDE = "alwaysHide" + ONLY_SHOW_SELECTED = "onlyShowSelected" + + +@control("NavigationBarDestination") +class NavigationBarDestination(AdaptiveControl): + """ + Defines the appearance of the button items that are arrayed within the + navigation bar. + + The value must be a list of two or more NavigationBarDestination instances. + """ + + label: Optional[str] = None + """ + The text label that appears below the icon of this `NavigationBarDestination`. + """ + + icon: Optional[IconValueOrControl] = None + """ + The [name of the icon](https://flet.dev/docs/reference/icons) or `Control` of the + destination. + + Example with icon name: + ``` + icon=ft.Icons.BOOKMARK + ``` + Example with Control: + ``` + icon=ft.Icon(ft.Icons.BOOKMARK) + ``` + + If `selected_icon` is provided, this will only be displayed when the destination is + not selected. + + To make the NavigationBar more accessible, consider choosing an icon with a stroked + and filled version, such as `ft.Icons.CLOUD` and `ft.Icons.CLOUD_QUEUE`. The icon + should be set to the stroked version and `selected_icon` to the filled version. + """ + + selected_icon: Optional[IconValueOrControl] = None + """ + The [name](https://flet.dev/docs/reference/icons) of alternative icon or `Control` + displayed when this destination is selected. + + Example with icon name: + ``` + selected_icon=ft.Icons.BOOKMARK + ``` + Example with Control: + ``` + selected_icon=ft.Icon(ft.Icons.BOOKMARK) + ``` + + If this icon is not provided, the NavigationBar will display `icon` in either state. + """ + + bgcolor: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of this destination. + """ + + +@control("NavigationBar") +class NavigationBar(ConstrainedControl, AdaptiveControl): + """ + Material 3 Navigation Bar component. + + Navigation bars offer a persistent and convenient way to switch between primary + destinations in an app. + + Online docs: https://flet.dev/docs/controls/navigationbar + """ + + destinations: list[NavigationBarDestination] = field(default_factory=list) + """ + Defines the appearance of the button items that are arrayed within the navigation + bar. + + The value must be a list of two or more `NavigationBarDestination` instances. + """ + + selected_index: int = 0 + """ + The index into `destinations` for the current selected `NavigationBarDestination` or + `None` if no destination is selected. + """ + + bgcolor: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the navigation bar itself. + """ + + label_behavior: Optional[NavigationBarLabelBehavior] = None + """ + Defines how the destinations' labels will be laid out and when they'll be displayed. + + Can be used to show all labels, show only the selected label, or hide all labels. + + Value is of type + [`NavigationBarLabelBehavior`](https://flet.dev/docs/reference/types/navigationbarlabelbehavior) + and defaults to `NavigationBarLabelBehavior.ALWAYS_SHOW`. + """ + + elevation: OptionalNumber = None + """ + The elevation of the navigation bar itself. + """ + + shadow_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used for the drop shadow to + indicate `elevation`. + """ + + indicator_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the selected destination + indicator. + """ + + indicator_shape: Optional[OutlinedBorder] = None + """ + The shape of the selected destination indicator. + + Value is of type + [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder). + """ + + surface_tint_color: OptionalColorValue = None + """ + The surface tint of the Material that holds the NavigationDrawer's contents. + """ + + border: Optional[Border] = None + """ + TBD + """ + + animation_duration: OptionalDurationValue = None + """ + The transition time for each destination as it goes between selected and unselected. + """ + + overlay_color: Optional[ControlStateValue[ColorValue]] = None + """ + The highlight [color](https://flet.dev/docs/reference/colors) of the + `NavigationDestination` in various + [`ControlState`](https://flet.dev/docs/reference/types/controlstate) states. + + The following [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + values are supported: `PRESSED`, `HOVERED` and `FOCUSED`. + """ + + on_change: OptionalControlEventHandler["NavigationBar"] = None + """ + Fires when selected destination changed. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/navigation_drawer.py b/sdk/python/packages/flet/src/flet/controls/material/navigation_drawer.py new file mode 100644 index 000000000..bdb975e89 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/navigation_drawer.py @@ -0,0 +1,163 @@ +from dataclasses import field +from enum import Enum +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.buttons import OutlinedBorder +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.dialog_control import DialogControl +from flet.controls.padding import OptionalPaddingValue +from flet.controls.types import ( + IconValueOrControl, + OptionalColorValue, + OptionalNumber, +) + +__all__ = [ + "NavigationDrawer", + "NavigationDrawerDestination", + "NavigationDrawerPosition", +] + + +@control("NavigationDrawerDestination") +class NavigationDrawerDestination(Control): + """ + Displays an icon with a label, for use in NavigationDrawer destinations. + """ + + label: Optional[str] = None + """ + The text label that appears below the icon of this `NavigationDrawerDestination`. + """ + + icon: Optional[IconValueOrControl] = None + """ + The [name of the icon](https://flet.dev/docs/reference/icons) or `Control` of the + destination. + + Example with icon name: + ``` + icon=ft.Icons.BOOKMARK + ``` + Example with Control: + ``` + icon=ft.Icon(ft.Icons.BOOKMARK) + ``` + + If `selected_icon` is provided, this will only be displayed when the destination is + not selected. + """ + + selected_icon: Optional[IconValueOrControl] = None + """ + The [name](https://flet.dev/docs/reference/icons) of alternative icon or `Control` + displayed when this destination is selected. + + Example with icon name: + ``` + selected_icon=ft.Icons.BOOKMARK + ``` + Example with Control: + ``` + selected_icon=ft.Icon(ft.Icons.BOOKMARK) + ``` + + If this icon is not provided, the NavigationDrawer will display `icon` in either + state. + """ + + bgcolor: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of this destination. + """ + + +class NavigationDrawerPosition(Enum): + START = "start" + END = "end" + + +@control("NavigationDrawer") +class NavigationDrawer(DialogControl): + """ + Material Design Navigation Drawer component. + + Navigation Drawer is a panel slides in horizontally from the left or right edge of + a page to show primary destinations in an app. + + Online docs: https://flet.dev/docs/controls/navigationdrawer + """ + + controls: list[Control] = field(default_factory=list) + """ + Defines the appearance of the items within the navigation drawer. + + The list contains `NavigationDrawerDestination` items and/or other controls such as + headlines and dividers. + """ + + selected_index: int = 0 + """ + The index for the current selected `NavigationDrawerDestination` or null if no + destination is selected. + + A valid selected_index is an integer between 0 and number of destinations - `1`. For + an invalid `selected_index`, for example, `-1`, all destinations will appear + unselected. + """ + + bgcolor: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the navigation drawer itself. + """ + + elevation: OptionalNumber = None + """ + The elevation of the navigation drawer itself. + """ + + indicator_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the selected destination + indicator. + """ + + indicator_shape: Optional[OutlinedBorder] = None + """ + The shape of the selected destination indicator. + + Value is of type + [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder). + """ + + shadow_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used for the drop shadow to + indicate `elevation`. + """ + + surface_tint_color: OptionalColorValue = None + """ + The surface tint of the Material that holds the NavigationDrawer's contents. + """ + + tile_padding: OptionalPaddingValue = None + """ + Defines the padding for `NavigationDrawerDestination` controls. + """ + + position: NavigationDrawerPosition = NavigationDrawerPosition.START + """ + The position of this drawer. + + Value is of type + [`NavigationDrawerPosition`](https://flet.dev/docs/reference/types/navigationdrawerposition) + and defaults to `NavigationDrawerPosition.START`. + """ + + on_change: OptionalControlEventHandler["NavigationDrawer"] = None + """ + Fires when selected destination changed. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/navigation_rail.py b/sdk/python/packages/flet/src/flet/controls/material/navigation_rail.py new file mode 100644 index 000000000..02299278f --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/navigation_rail.py @@ -0,0 +1,264 @@ +from dataclasses import field +from enum import Enum +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.buttons import OutlinedBorder +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.padding import OptionalPaddingValue +from flet.controls.text_style import TextStyle +from flet.controls.types import ( + IconValueOrControl, + OptionalColorValue, + OptionalNumber, + StrOrControl, +) + +__all__ = ["NavigationRail", "NavigationRailDestination", "NavigationRailLabelType"] + + +class NavigationRailLabelType(Enum): + NONE = "none" + ALL = "all" + SELECTED = "selected" + + +@control("NavigationRailDestination") +class NavigationRailDestination(Control): + """ + TBD + """ + + icon: Optional[IconValueOrControl] = None + """ + The [name of the icon](https://flet.dev/docs/reference/icons) or `Control` of the + destination. + + Example with icon name: + ``` + icon=ft.Icons.BOOKMARK + ``` + Example with Control: + ``` + icon=ft.Icon(ft.Icons.BOOKMARK) + ``` + + If `selected_icon` is provided, this will only be displayed when the destination is + not selected. + + To make the NavigationRail more accessible, consider choosing an icon with a stroked + and filled version, such as `ft.Icons.CLOUD` and `ft.Icons.CLOUD_QUEUE`. The icon + should be set to the stroked version and `selected_icon` to the filled version. + """ + + selected_icon: Optional[IconValueOrControl] = None + """ + The [name](https://flet.dev/docs/reference/icons) of alternative icon or `Control` + displayed when this destination is selected. + + Example with icon name: + ``` + selected_icon=ft.Icons.BOOKMARK + ``` + Example with Control: + ``` + selected_icon=ft.Icon(ft.Icons.BOOKMARK) + ``` + + If this icon is not provided, the NavigationRail will display `icon` in either + state. + """ + + label: Optional[StrOrControl] = None + """ + A string or Control representing the destination's label. + """ + + padding: OptionalPaddingValue = None + """ + The amount of space to inset the destination item. + + Padding is an instance of + [`Padding`](https://flet.dev/docs/reference/types/padding) class. + """ + + indicator_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the `indicator_shape` when + this destination is selected. + """ + + indicator_shape: Optional[OutlinedBorder] = None + """ + The shape of the selection indicator. The value is an instance of + [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder) class. + """ + + +@control("NavigationRail") +class NavigationRail(ConstrainedControl): + """ + A material widget that is meant to be displayed at the left or right of an app to + navigate between a small number of views, typically between three and five. + + Online docs: https://flet.dev/docs/controls/navigationrail + """ + + destinations: list[NavigationRailDestination] = field(default_factory=list) + """ + Defines the appearance of the button items that are arrayed within the navigation + rail. + + The value must be a list of two or more `NavigationRailDestination` instances. + """ + + elevation: OptionalNumber = None + """ + Controls the size of the shadow below the NavigationRail. + + Defaults to `0.0`. + """ + + selected_index: Optional[int] = None + """ + The index into `destinations` for the current selected `NavigationRailDestination` + or `None` if no destination is selected. + """ + + extended: bool = False + """ + Indicates that the NavigationRail should be in the extended state. + + The extended state has a wider rail container, and the labels are positioned next to + the icons. `min_extended_width` can be used to set the minimum width of the rail + when it is in this state. + + The rail will implicitly animate between the extended and normal state. + + If the rail is going to be in the extended state, then the `label_type` must be set + to `none`. + + Defaults to `False`. + """ + + label_type: Optional[NavigationRailLabelType] = None + """ + Defines the layout and behavior of the labels for the default, unextended navigation + rail. + + When a navigation rail is extended, the labels are always shown. + + Value is of type + [`NavigationRailLabelType`](https://flet.dev/docs/reference/types/navigationraillabeltype) + and defaults to `None` - no labels are shown. + """ + + bgcolor: OptionalColorValue = None + """ + Sets the [color](https://flet.dev/docs/reference/colors) of the Container that holds + all of the NavigationRail's contents. + """ + + indicator_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the navigation rail's + indicator. + """ + + indicator_shape: Optional[OutlinedBorder] = None + """ + The shape of the navigation rail's indicator. + + Value is of type + [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder) and + defaults to `StadiumBorder()`. + """ + + leading: Optional[Control] = None + """ + An optional leading control in the rail that is placed above the destinations. + + Its location is not affected by `group_alignment`. + + This is commonly a + [`FloatingActionButton`](https://flet.dev/docs/controls/floatingactionbutton), but + may also be a non-button, such as a logo. + """ + + trailing: Optional[Control] = None + """ + An optional trailing control in the rail that is placed below the destinations. + + Its location is affected by `group_alignment`. + + This is commonly a list of additional options or destinations that is usually only + rendered when `extended=True`. + """ + + min_width: OptionalNumber = None + """ + The smallest possible width for the rail regardless of the destination's icon or + label size. + + Defaults to `72`. + + This value also defines the min width and min height of the destinations. + + To make a compact rail, set this to `56` and use `label_type='none'`. + """ + + min_extended_width: OptionalNumber = None + """ + The final width when the animation is complete for setting `extended` to `True`. + + Defaults to `256`. + """ + + group_alignment: OptionalNumber = None + """ + The vertical alignment for the group of destinations within the rail. + + The NavigationRailDestinations are grouped together with the trailing widget, + between the leading widget and the bottom of the rail. + + The value must be between `-1.0` and `1.0`. + + If `group_alignment` is `-1.0`, then the items are aligned to the top. If + `group_alignment` is `0.0`, then the items are aligned to the center. If + `group_alignment` is `1.0`, then the items are aligned to the bottom. + + Defaults to `-1.0`. + """ + + selected_label_text_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) of a + destination's label when it is selected. + + When a destination is not selected, `unselected_label_text_style` will instead be + used. + """ + + unselected_label_text_style: Optional[TextStyle] = None + """ + The [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) of a + destination's label when it is not selected. + + When a destination is selected, `selected_label_text_style` will instead be used. + """ + + on_change: OptionalControlEventHandler["NavigationRail"] = None + """ + Fires when selected destination changed. + """ + + def before_update(self): + super().before_update() + if self.elevation is not None: + assert self.elevation >= 0, "elevation cannot be negative" + if self.min_width is not None: + assert self.min_width >= 0, "min_width cannot be negative" + if self.min_extended_width is not None: + assert self.min_extended_width >= 0, "min_extended_width cannot be negative" diff --git a/sdk/python/packages/flet/src/flet/controls/material/outlined_button.py b/sdk/python/packages/flet/src/flet/controls/material/outlined_button.py new file mode 100644 index 000000000..6ba0df8f7 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/outlined_button.py @@ -0,0 +1,123 @@ +import asyncio +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.buttons import ButtonStyle +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.types import ( + ClipBehavior, + IconValueOrControl, + OptionalColorValue, + StrOrControl, + UrlTarget, +) + +__all__ = ["OutlinedButton"] + + +@control("OutlinedButton") +class OutlinedButton(ConstrainedControl, AdaptiveControl): + """ + Outlined buttons are medium-emphasis buttons. They contain actions that are + important, but aren’t the primary action in an app. Outlined buttons pair well with + filled buttons to indicate an alternative, secondary action. + + Online docs: https://flet.dev/docs/controls/outlinedbutton + """ + + content: Optional[StrOrControl] = None + """ + A Control representing custom button content. + """ + + icon: Optional[IconValueOrControl] = None + """ + Icon shown in the button. + """ + + icon_color: OptionalColorValue = None + """ + Icon [color](https://flet.dev/docs/reference/colors). + """ + + style: Optional[ButtonStyle] = None + """ + The value is an instance of [`ButtonStyle`](https://flet.dev/docs/reference/types/buttonstyle) + class. + """ + + autofocus: bool = False + """ + True if the control will be selected as the initial focus. + + If there is more than one control on a page with autofocus set, then the first one + added to the page will get focus. + """ + + clip_behavior: ClipBehavior = ClipBehavior.NONE + """ + The content will be clipped (or not) according to this option. + + Value is of type [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) + and defaults to `ClipBehavior.NONE`. + """ + + url: Optional[str] = None + """ + The URL to open when the button is clicked. + + If registered, `on_click` event is fired after that. + """ + + url_target: Optional[UrlTarget] = None + """ + Where to open URL in the web mode. + + Value is of type [`UrlTarget`](https://flet.dev/docs/reference/types/urltarget) and + defaults to `UrlTarget.BLANK`. + """ + + on_click: OptionalControlEventHandler["OutlinedButton"] = None + """ + Fires when a user clicks the button. + """ + + on_long_press: OptionalControlEventHandler["OutlinedButton"] = None + """ + Fires when the button is long-pressed. + """ + + on_hover: OptionalControlEventHandler["OutlinedButton"] = None + """ + Fires when a mouse pointer enters or exists the button response area. + + `data` property of event object contains `true` (string) when cursor enters and + `false` when it exits. + """ + + on_focus: OptionalControlEventHandler["OutlinedButton"] = None + """ + Fires when the control has received focus. + """ + + on_blur: OptionalControlEventHandler["OutlinedButton"] = None + """ + Fires when the control has lost focus. + """ + + def before_update(self): + super().before_update() + assert ( + self.icon + or isinstance(self.content, str) + or (isinstance(self.content, Control) and self.content.visible) + ), "at minimum, icon or a visible content must be provided" + + async def focus_async(self): + await self._invoke_method_async("focus") + + def focus(self): + asyncio.create_task(self.focus_async()) diff --git a/sdk/python/packages/flet/src/flet/controls/material/popup_menu_button.py b/sdk/python/packages/flet/src/flet/controls/material/popup_menu_button.py new file mode 100644 index 000000000..5fc15e37a --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/popup_menu_button.py @@ -0,0 +1,221 @@ +from dataclasses import field +from enum import Enum +from typing import Optional + +from flet.controls.animation import AnimationStyle +from flet.controls.base_control import control +from flet.controls.box import BoxConstraints +from flet.controls.buttons import ButtonStyle, OutlinedBorder +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.padding import OptionalPaddingValue, PaddingValue +from flet.controls.types import ( + ClipBehavior, + IconValueOrControl, + MouseCursor, + Number, + OptionalColorValue, + OptionalNumber, + StrOrControl, +) + + +class PopupMenuPosition(Enum): + OVER = "over" + UNDER = "under" + + +@control("PopupMenuItem") +class PopupMenuItem(Control): + content: Optional[StrOrControl] = None + """ + A `Control` representing custom content of this menu item. + """ + + icon: Optional[IconValueOrControl] = None + """ + An icon to draw before the text label of this menu item. + """ + + checked: Optional[bool] = None + """ + If set to `True` or `False` a menu item draws a checkmark. + """ + + height: Number = 48.0 + """ + The minimum height of this menu item. + + Defaults to `48`. + """ + + padding: OptionalPaddingValue = None + """ + The padding of this menu item. + + Note that the `height` value of this menu item may influence the applied padding. + For example, if a `height` greater than the height of the sum of the padding and a + `content` is provided, then the padding's effect will not be visible. + + Padding value is an instance of [`Padding`](https://flet.dev/docs/reference/types/padding) + class. + + Defaults to `padding.symmetric(horizontal=12)`. + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + The cursor to be displayed when a mouse pointer enters or is hovering over this + control. + + Value is of type [`MouseCursor`](https://flet.dev/docs/reference/types/mousecursor). + """ + + on_click: OptionalControlEventHandler["PopupMenuItem"] = None + """ + Called when a user clicks on this menu item. + """ + + +@control("PopupMenuButton") +class PopupMenuButton(ConstrainedControl): + """ + An icon button which displays a menu when clicked. + + Online docs: https://flet.dev/docs/controls/popupmenubutton + """ + + content: Optional[StrOrControl] = None + """ + A `Control` that will be displayed instead of "more" icon. + """ + + items: list[PopupMenuItem] = field(default_factory=list) + """ + A collection of `PopupMenuItem` controls to display in a dropdown menu. + """ + + icon: Optional[IconValueOrControl] = None + """ + If provided, an icon to draw on the button. + """ + + bgcolor: OptionalColorValue = None + """ + The menu's background [color](https://flet.dev/docs/reference/colors). + """ + + icon_color: OptionalColorValue = None + """ + The `icon`'s [color](https://flet.dev/docs/reference/colors). + """ + + shadow_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used to paint the shadow below + the menu. + """ + + surface_tint_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used as an overlay on color to + indicate elevation. + """ + + icon_size: OptionalNumber = None + """ + The `icon`'s size. + """ + + splash_radius: OptionalNumber = None + """ + The splash radius. + """ + + elevation: OptionalNumber = None + """ + The menu's elevation when opened. + + Defaults to `8`. + """ + + menu_position: Optional[PopupMenuPosition] = None + """ + Defines position of the popup menu relative to the button. + + Value is of type + [`PopupMenuPosition`](https://flet.dev/docs/reference/types/popupmenuposition) and + defaults to `PopupMenuPosition.OVER`. + """ + + clip_behavior: ClipBehavior = ClipBehavior.NONE + """ + The `content` will be clipped (or not) according to this option. + + Value is of type + [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) and defaults to + `ClipBehavior.NONE`. + """ + + enable_feedback: Optional[bool] = None + """ + Whether detected gestures should provide acoustic and/or haptic feedback. + + On Android, for example, setting this to `True` produce a click sound and a + long-press will produce a short vibration. + + Defaults to `True`. + """ + + shape: Optional[OutlinedBorder] = None + """ + The menu's shape. + + Value is of type [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder) + and defaults to `CircleBorder(radius=10.0)`. + """ + + padding: PaddingValue = 8 + """ + Value is of type [`Padding`](https://flet.dev/docs/reference/types/padding) and + defaults to `Padding.all(8.0)`. + """ + + menu_padding: OptionalPaddingValue = None + """ + TBD + """ + + style: Optional[ButtonStyle] = None + """ + TBD + """ + + popup_animation_style: Optional[AnimationStyle] = None + """ + TBD + """ + + size_constraints: Optional[BoxConstraints] = None + """ + TBD + """ + + on_open: OptionalControlEventHandler["PopupMenuButton"] = None + """ + Called when the popup menu is shown. + """ + + on_cancel: OptionalControlEventHandler["PopupMenuButton"] = None + """ + Called when the user dismisses/cancels the popup menu without selecting an item. + """ + + on_select: OptionalControlEventHandler["PopupMenuButton"] = None + """ + TBD + """ + + def __contains__(self, item): + return item in self.items diff --git a/sdk/python/packages/flet/src/flet/controls/material/progress_bar.py b/sdk/python/packages/flet/src/flet/controls/material/progress_bar.py new file mode 100644 index 000000000..bd185ec32 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/progress_bar.py @@ -0,0 +1,101 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.border_radius import OptionalBorderRadiusValue +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.types import OptionalColorValue, OptionalNumber + +__all__ = ["ProgressBar"] + + +@control("ProgressBar") +class ProgressBar(ConstrainedControl): + """ + A material design linear progress indicator, also known as a progress bar. + + A control that shows progress along a line. + + Online docs: https://flet.dev/docs/controls/progressbar + """ + + value: OptionalNumber = None + """ + The value of this progress indicator. + + A value of `0.0` means no progress and `1.0` means that progress is complete. The + value will be clamped to be in the range `0.0` - `1.0`. + + Defaults to `None`, meaning that this progress indicator is indeterminate - + displays a predetermined animation that does not indicate how much actual progress + is being made. + """ + + bar_height: OptionalNumber = 4.0 + """ + The minimum height of the line used to draw the linear indicator. + + Defaults to `4`. + """ + + color: OptionalColorValue = None + """ + The progress indicator's [color](https://flet.dev/docs/reference/colors). + """ + + bgcolor: OptionalColorValue = None + """ + [Color](https://flet.dev/docs/reference/colors) of the track being filled by the + linear indicator. + """ + + border_radius: OptionalBorderRadiusValue = None + """ + The border radius of both the indicator and the track. + + Border radius is an instance of + [`BorderRadius`](https://flet.dev/docs/reference/types/borderradius) class. + + Defaults to `border_radius.all(0)` - rectangular shape. + """ + + semantics_label: Optional[str] = None + """ + The [`Semantics.label`](https://flet.dev/docs/controls/semantics#label) for this + progress indicator. + """ + + semantics_value: OptionalNumber = None + """ + The [`Semantics.value`](https://flet.dev/docs/controls/semantics#value) for this + progress indicator. + """ + + stop_indicator_color: OptionalColorValue = None + """ + TBD + """ + + stop_indicator_radius: OptionalNumber = None + """ + TBD + """ + + track_gap: OptionalNumber = None + """ + TBD + """ + + year_2023: Optional[bool] = None + """ + TBD + """ + + def before_update(self): + super().before_update() + assert self.value is None or self.value >= 0, "value cannot be negative" + assert ( + self.bar_height is None or self.bar_height >= 0 + ), "bar_height cannot be negative" + assert ( + self.semantics_value is None or self.semantics_value >= 0 + ), "semantics_value cannot be negative" diff --git a/sdk/python/packages/flet/src/flet/controls/material/progress_ring.py b/sdk/python/packages/flet/src/flet/controls/material/progress_ring.py new file mode 100644 index 000000000..225abd3fe --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/progress_ring.py @@ -0,0 +1,94 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.box import BoxConstraints +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.padding import PaddingValue +from flet.controls.types import Number, OptionalColorValue, OptionalNumber, StrokeCap + +__all__ = ["ProgressRing"] + + +@control("ProgressRing") +class ProgressRing(ConstrainedControl): + """ + A material design circular progress indicator, which spins to indicate that the + application is busy. + + A control that shows progress along a circle. + + Online docs: https://flet.dev/docs/controls/progressring + """ + + value: OptionalNumber = None + """ + The value of this progress indicator. + + A value of `0.0` means no progress and `1.0` means that progress is complete. + The value will be clamped to be in the range `0.0` - `1.0`. If `None`, this + progress indicator is indeterminate, which means the indicator displays a + predetermined animation that does not indicate how much actual progress is being + made. + """ + + stroke_width: Number = 4.0 + """ + The width of the line used to draw the circle. + """ + + color: OptionalColorValue = None + """ + The progress indicator's [color](https://flet.dev/docs/reference/colors). + """ + + bgcolor: OptionalColorValue = None + """ + [Color](https://flet.dev/docs/reference/colors) of the circular track being filled + by the circular indicator. + """ + + stroke_align: Number = 0.0 + """ + The relative position of the stroke. + + Value typically ranges be `-1.0` (inside stroke) and `1.0` (outside stroke). + + Defaults to `0` - centered. + """ + + stroke_cap: Optional[StrokeCap] = None + """ + The progress indicator's line ending. + + Value is of type [`StrokeCap`](https://flet.dev/docs/reference/types/strokecap). + """ + + semantics_label: Optional[str] = None + """ + The `Semantics.label` for this progress indicator. + """ + + semantics_value: OptionalNumber = None + """ + The `Semantics.value` for this progress indicator. + """ + + track_gap: OptionalNumber = None + """ + TBD + """ + + size_constraints: Optional[BoxConstraints] = None + """ + TBD + """ + + padding: Optional[PaddingValue] = None + """ + TBD + """ + + year_2023: Optional[bool] = None + """ + TBD + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/radio.py b/sdk/python/packages/flet/src/flet/controls/material/radio.py new file mode 100644 index 000000000..a16d74623 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/radio.py @@ -0,0 +1,123 @@ +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.control_state import ControlStateValue +from flet.controls.text_style import TextStyle +from flet.controls.types import ( + ColorValue, + LabelPosition, + MouseCursor, + OptionalColorValue, + OptionalNumber, + VisualDensity, +) + +__all__ = ["Radio"] + + +@control("Radio") +class Radio(ConstrainedControl, AdaptiveControl): + """ + Radio buttons let people select a single option from two or more choices. + + Online docs: https://flet.dev/docs/controls/radio + """ + + label: str = "" + """ + The clickable label to display on the right of a Radio. + """ + + label_position: LabelPosition = LabelPosition.RIGHT + """ + Value is of type [`LabelPosition`](https://flet.dev/docs/reference/types/labelposition) + and defaults to `LabelPosition.RIGHT`. + """ + + label_style: Optional[TextStyle] = None + """ + The label's style. + + Value is of type [`TextStyle`](https://flet.dev/docs/reference/types/textstyle). + """ + + value: Optional[str] = None + """ + The value to set to containing `RadioGroup` when the radio is selected. + """ + + autofocus: bool = False + """ + True if the control will be selected as the initial focus. + + If there is more than one control on a page with autofocus set, then the first one + added to the page will get focus. + """ + + fill_color: Optional[ControlStateValue[ColorValue]] = None + """ + The [color](https://flet.dev/docs/reference/colors) that fills the radio, in all or + specific [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + states. + """ + + active_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) used to fill this radio when it + is selected. + """ + + overlay_color: Optional[ControlStateValue[ColorValue]] = None + """ + The overlay [color](https://flet.dev/docs/reference/colors) of this radio in all or + specific [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + states. + """ + + hover_color: OptionalColorValue = None + """ + The color of this radio when it is hovered. + """ + + focus_color: OptionalColorValue = None + """ + The color of this radio when it has the input focus. + """ + + splash_radius: OptionalNumber = None + """ + The splash radius of the circular Material ink response. + """ + + toggleable: bool = False + """ + Set to `True` if this radio button is allowed to be returned to an indeterminate + state by selecting it again when selected. + """ + + visual_density: Optional[VisualDensity] = None + """ + Defines how compact the radio's layout will be. + + Value is of type [`VisualDensity`](https://flet.dev/docs/reference/types/visualdensity). + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + The cursor for a mouse pointer entering or hovering over this control. + + Value is of type [`MouseCursor`](https://flet.dev/docs/reference/types/mousecursor). + """ + + on_focus: OptionalControlEventHandler["Radio"] = None + """ + Fires when the control has received focus. + """ + + on_blur: OptionalControlEventHandler["Radio"] = None + """ + Fires when the control has lost focus. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/radio_group.py b/sdk/python/packages/flet/src/flet/controls/material/radio_group.py new file mode 100644 index 000000000..62284ce68 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/radio_group.py @@ -0,0 +1,38 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler + +__all__ = ["RadioGroup"] + + +@control("RadioGroup") +class RadioGroup(Control): + """ + Radio buttons let people select a single option from two or more choices. + + Online docs: https://flet.dev/docs/controls/radio + """ + + content: Control + """ + The content of the RadioGroup. + + Typically a list of `Radio` controls nested in a container control, e.g. `Column`, + `Row`. + """ + + value: Optional[str] = None + """ + Current value of the RadioGroup. + """ + + on_change: OptionalControlEventHandler["RadioGroup"] = None + """ + Fires when the state of the RadioGroup is changed. + """ + + def before_update(self): + super().before_update() + assert self.content.visible, "content must be visible" diff --git a/sdk/python/packages/flet/src/flet/controls/material/range_slider.py b/sdk/python/packages/flet/src/flet/controls/material/range_slider.py new file mode 100644 index 000000000..fc72b6541 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/range_slider.py @@ -0,0 +1,150 @@ +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.control_state import ControlStateValue +from flet.controls.types import ( + ColorValue, + MouseCursor, + Number, + OptionalColorValue, + OptionalNumber, +) + +__all__ = ["RangeSlider"] + + +@control("RangeSlider") +class RangeSlider(ConstrainedControl): + """ + A Material Design range slider. Used to select a range from a range of values. + A range slider can be used to select from either a continuous or a discrete + set of values. + The default is to use a continuous range of values from min to max. + + Online docs: https://flet.dev/docs/controls/rangeslider + """ + + start_value: Number + """ + The currently selected start value for the slider. + + The slider's left thumb is drawn at a position that corresponds to this value. + """ + + end_value: Number + """ + The currently selected end value for the slider. + + The slider's right thumb is drawn at a position that corresponds to this value. + """ + + label: Optional[str] = None + """ + A label to show above the slider thumbs when the slider is active. The value of + `label` may contain `{value}` which will be replaced with a current slider + `start_value` and `end_value`. + + If not set, then the labels will not be displayed. + """ + + min: OptionalNumber = None + """ + The minimum value the user can select. + + Defaults to `0.0`. Must be less than or equal to `max`. + + If the `max` is equal to the `min`, then the slider is disabled. + """ + + max: OptionalNumber = None + """ + The maximum value the user can select. Must be greater than or equal to `min`. + + If the `max` is equal to the `min`, then the slider is disabled. + + Defaults to `1.0`. + """ + + divisions: Optional[int] = None + """ + The number of discrete divisions. + + Typically used with `label` to show the current discrete values. + + If not set, the slider is continuous. + """ + + round: Optional[int] = None + """ + The number of decimals displayed on the `label` containing `{value}`. + + The default is 0 (displays value rounded to the nearest integer). + """ + + active_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use for the portion of the + slider track that is active. + + The "active" segment of the range slider is the span between the thumbs. + """ + + inactive_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) for the inactive portions of + the slider track. + + The "inactive" segments of the slider are the span of tracks between the min and + the start thumb, and the end thumb and the max. + """ + + overlay_color: Optional[ControlStateValue[ColorValue]] = None + """ + The highlight [color](https://flet.dev/docs/reference/colors) that's typically + used to indicate that the range slider thumb is in `HOVERED` or `DRAGGED` + [`ControlState`](https://flet.dev/docs/reference/types/controlstate)s. + """ + + mouse_cursor: Optional[ControlStateValue[MouseCursor]] = None + """ + The cursor for a mouse pointer entering or hovering over this control. + + It's value can be made to depend on the slider's + [`ControlState`](https://flet.dev/docs/reference/types/controlstate). + + Value is of type + [`MouseCursor`](https://flet.dev/docs/reference/types/mousecursor). + """ + + on_change: OptionalControlEventHandler["RangeSlider"] = None + """ + Fires when the state of the Slider is changed. + """ + + on_change_start: OptionalControlEventHandler["RangeSlider"] = None + """ + Fires when the user starts selecting a new value for the slider. + """ + + on_change_end: OptionalControlEventHandler["RangeSlider"] = None + """ + Fires when the user is done selecting a new value for the slider. + """ + + def before_update(self): + if self.max is not None: + assert self.end_value <= self.max, ( + "end_value must be less than or equal to max" + ) + + if self.min is not None: + assert self.start_value >= self.min, ( + "start_value must be greater than or equal to min" + ) + + assert self.start_value <= self.end_value, ( + "start_value must be less than or equal to end_value" + ) + pass diff --git a/sdk/python/packages/flet/src/flet/controls/material/reorderable_list_view.py b/sdk/python/packages/flet/src/flet/controls/material/reorderable_list_view.py new file mode 100644 index 000000000..46f3e47a0 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/reorderable_list_view.py @@ -0,0 +1,178 @@ +from dataclasses import dataclass, field +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.control_event import ( + Event, + OptionalEventHandler, +) +from flet.controls.core.list_view import ListView +from flet.controls.padding import OptionalPaddingValue +from flet.controls.types import ( + ClipBehavior, + MouseCursor, + Number, + OptionalNumber, +) + + +@dataclass +class OnReorderEvent(Event["ReorderableListView"]): + new_index: Optional[int] + old_index: Optional[int] + + +@control("ReorderableListView") +class ReorderableListView(ListView): + """ + A scrollable list of controls that can be reordered. + + Online docs: https://flet.dev/docs/controls/reorderablelistview + """ + + controls: list[Control] = field(default_factory=list) + """ + The controls to be reordered. + """ + + horizontal: bool = False + """ + Whether the `controls` should be laid out horizontally. + + Defaults to `False`. + """ + + reverse: bool = False + """ + Whether the scroll view scrolls in the reading direction. + + For example, if the reading direction is left-to-right and `horizontal` is `True`, + then the scroll view scrolls from left to right when `reverse` is `False` + and from right to left when `reverse` is `True`. + + Similarly, if `horizontal` is `False`, then the scroll view scrolls from top + to bottom when `reverse` is `False` and from bottom to top when `reverse` is `True`. + + Defaults to `False`. + """ + + item_extent: OptionalNumber = None + """ + If non-null, forces the children to have the given extent in the scroll direction. + + Specifying an `item_extent` is more efficient than letting the children determine + their own extent because the scrolling machinery can make use of the foreknowledge + of the children's extent to save work, for example when the scroll position + changes drastically. + """ + + first_item_prototype: bool = False + """ + `True` if the dimensions of the first item should be used as a "prototype" for all + other items, i.e. their height or width will be the same as the first item. + + Defaults to `False`. + """ + + padding: OptionalPaddingValue = None + """ + The amount of space by which to inset the `controls`. + + Value is of type [`Padding`](https://flet.dev/docs/reference/types/padding). + """ + + clip_behavior: ClipBehavior = ClipBehavior.HARD_EDGE + """ + The content will be clipped (or not) according to this option. + + Value is of type + [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) and defaults + to `ClipBehavior.HARD_EDGE`. + """ + + cache_extent: OptionalNumber = None + """ + The viewport has an area before and after the visible area to cache items that are + about to become visible when the user scrolls. + + Items that fall in this cache area are laid out even though they are not (yet) + visible on screen. The `cache_extent` describes how many pixels the cache area + extends before the leading edge and after the trailing edge of the viewport. + + The total extent, which the viewport will try to cover with children, is + `cache_extent` before the leading edge + extent of the main axis + `cache_extent` + after the trailing edge. + + The cache area is also used to implement implicit accessibility scrolling on iOS: + When the accessibility focus moves from an item in the visible viewport to an + invisible item in the cache area, the framework will bring that item into view + with an (implicit) scroll action. + """ + + anchor: Number = 0.0 + """ + The relative position of the zero scroll offset. + + Defaults to `0.0`. + """ + + auto_scroller_velocity_scalar: OptionalNumber = None + """ + The velocity scalar per pixel over scroll. It represents how the velocity scale + with the over scroll distance. The auto-scroll velocity = (distance of overscroll) + * velocity scalar. + """ + + header: Optional[Control] = None + """ + A non-reorderable header item to show before the `controls`. + + Value is of type `Control`. + """ + + footer: Optional[Control] = None + """ + A non-reorderable footer item to show after the `controls`. + + Value is of type `Control`. + """ + + build_controls_on_demand: bool = True + """ + Whether the `controls` should be built lazily/on-demand, i.e. only when they are + about to become visible. + + This is particularly useful when dealing with a large number of controls. + + Defaults to `True`. + """ + + show_default_drag_handles: bool = True + """ + TBD + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + TBD + """ + + on_reorder: OptionalEventHandler[OnReorderEvent] = None + """ + Fires when a child control has been dragged to a new location in the list and the + application should update the order of the items. + """ + + on_reorder_start: OptionalEventHandler[OnReorderEvent] = None + """ + Fires when an item drag has started. + + Event handler argument is of type + [`OnReorderEvent`](https://flet.dev/docs/reference/types/onreorderevent). + """ + + on_reorder_end: OptionalEventHandler[OnReorderEvent] = None + """ + Fires when the dragged item is dropped. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/search_bar.py b/sdk/python/packages/flet/src/flet/controls/material/search_bar.py new file mode 100644 index 000000000..193f89f60 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/search_bar.py @@ -0,0 +1,303 @@ +import asyncio +from dataclasses import field +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.border import BorderSide, OptionalBorderSide +from flet.controls.box import OptionalBoxConstraints +from flet.controls.buttons import OptionalOutlinedBorder, OutlinedBorder +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control, OptionalControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.control_state import ControlStateValue +from flet.controls.material.textfield import KeyboardType, TextCapitalization +from flet.controls.padding import PaddingValue +from flet.controls.text_style import OptionalTextStyle, TextStyle +from flet.controls.types import ( + ColorValue, + OptionalColorValue, + OptionalNumber, + OptionalString, +) + +__all__ = ["SearchBar"] + + +@control("SearchBar") +class SearchBar(ConstrainedControl): + """ + Manages a "search view" route that allows the user to select one of the suggested + completions for a search query. + + Online docs: https://flet.dev/docs/controls/searchbar + """ + + controls: list[Control] = field(default_factory=list) + """ + The list of controls to be displayed below the search bar when in search view. + These controls are usually `ListTile`s and will be displayed in a `ListView`. + """ + + value: str = "" + """ + The text in the search bar. + """ + + bar_leading: OptionalControl = None + """ + A `Control` to display before the text input field when the search view is close. + This is typically an `Icon` or an `IconButton`. + """ + + bar_trailing: Optional[list[Control]] = None + """ + A `Control` to display after the text input field when the search view is close. + + These controls can represent additional modes of searching (e.g voice search), + an avatar, or an overflow menu and are usually not more than two. + """ + + bar_hint_text: OptionalString = None + """ + Defines the text to be shown in the search bar when it is empty and the search + view is close. Usually some text that suggests what sort of input the field + accepts. + """ + + bar_bgcolor: Optional[ControlStateValue[ColorValue]] = None + """ + Defines the background [color](https://flet.dev/docs/reference/colors) of the + search bar in all or specific + [`ControlState`](https://flet.dev/docs/reference/types/controlstate) states. + """ + + bar_overlay_color: Optional[ControlStateValue[ColorValue]] = None + """ + Defines the highlight [color](https://flet.dev/docs/reference/colors) that's + typically used to indicate that the search bar is in `FOCUSED`, `HOVERED`, or + `PRESSED` [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + states. + """ + + bar_shadow_color: Optional[ControlStateValue[ColorValue]] = None + """ + TBD + """ + + bar_surface_tint_color: Optional[ControlStateValue[ColorValue]] = None + """ + TBD + """ + + bar_elevation: Optional[ControlStateValue[OptionalNumber]] = None + """ + TBD + """ + + bar_border_side: Optional[ControlStateValue[BorderSide]] = None + """ + TBD + """ + + bar_shape: Optional[ControlStateValue[OutlinedBorder]] = None + """ + TBD + """ + + bar_text_style: Optional[ControlStateValue[TextStyle]] = None + """ + TBD + """ + + bar_hint_text_style: Optional[ControlStateValue[TextStyle]] = None + """ + TBD + """ + + bar_padding: Optional[ControlStateValue[PaddingValue]] = None + """ + TBD + """ + + bar_scroll_padding: PaddingValue = 20 + """ + Configures the padding around a Scrollable when the text field scrolls into view. + + If the bar's text field is partially off-screen or covered (e.g., by the + keyboard), it scrolls into view, ensuring it is positioned at the specified + distance from the Scrollable edges. + + Value is of type [`Padding`](https://flet.dev/docs/reference/types/padding). + """ + + view_leading: OptionalControl = None + """ + A `Control` to display before the text input field when the search view is open. + Typically an `Icon` or an `IconButton`. + + Defaults to a back button which closes/pops the search view. + """ + + view_trailing: Optional[list[Control]] = None + """ + A list of `Control`s to display after the text input field when the search view is + open. + + Defaults to a close button which closes/pops the search view. + """ + + view_elevation: OptionalNumber = None + """ + Defines the elevation of the search view. + """ + + view_bgcolor: OptionalColorValue = None + """ + Defines the background [color](https://flet.dev/docs/reference/colors) of the + search view. + """ + + view_hint_text: OptionalString = None + """ + Defines the text to be displayed when the search bar's input field is empty. + """ + + view_side: OptionalBorderSide = None + """ + Defines the color and weight of the search view's outline. + + Value is of type [`BorderSide`](https://flet.dev/docs/reference/types/borderside). + """ + + view_shape: OptionalOutlinedBorder = None + """ + Defines the shape of the search view. + + Value is of type + [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder). + """ + + view_header_text_style: OptionalTextStyle = None + """ + Defines the [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) of the + text being edited on the search view. + """ + + view_hint_text_style: OptionalTextStyle = None + """ + Defines the [`TextStyle`](https://flet.dev/docs/reference/types/textstyle) of + `view_hint_text`. + """ + + view_size_constraints: OptionalBoxConstraints = None + """ + TBD + """ + + view_header_height: OptionalNumber = None + """ + TBD + """ + + divider_color: OptionalColorValue = None + """ + The color of the divider when in search view. + """ + + capitalization: Optional[TextCapitalization] = None + """ + Enables automatic on-the-fly capitalization of entered text. + + Value is of type + [`TextCapitalization`](https://flet.dev/docs/reference/types/textcapitalization). + """ + + full_screen: bool = False + """ + Defines whether the search view grows to fill the entire screen when the search + bar is tapped. Defaults to `False`. + """ + + keyboard_type: KeyboardType = KeyboardType.TEXT + """ + The type of action button to use for the keyboard. + + Value is of type + [`KeyboardType`](https://flet.dev/docs/reference/types/keyboardtype) and defaults + to `KeyboardType TEXT`. + """ + + view_surface_tint_color: OptionalColorValue = None + """ + Defines the color of the search view's surface tint. + """ + + autofocus: bool = False + """ + Whether the text field should focus itself if nothing else is already focused. + + Defaults to `False`. + """ + + on_tap: OptionalControlEventHandler["SearchBar"] = None + """ + Fires when the search bar is tapped. + """ + + on_tap_outside_bar: OptionalControlEventHandler["SearchBar"] = None + """ + TBD + """ + + on_submit: OptionalControlEventHandler["SearchBar"] = None + """ + Fires when user presses ENTER while focus is on SearchBar. + """ + + on_change: OptionalControlEventHandler["SearchBar"] = None + """ + Fires when the typed input in the search bar has changed. + """ + + on_focus: OptionalControlEventHandler["SearchBar"] = None + """ + TBD + """ + + on_blur: OptionalControlEventHandler["SearchBar"] = None + """ + TBD + """ + + def __contains__(self, item): + return item in self.controls + + def before_update(self): + super().before_update() + + # Public methods + async def focus_async(self): + await self._invoke_method_async("focus") + + def focus(self): + asyncio.create_task(self.focus_async()) + + async def blur_async(self): + await self._invoke_method_async("blur") + + def blur(self): + asyncio.create_task(self.blur_async()) + + def open_view(self): + asyncio.create_task(self.open_view_async()) + + async def open_view_async(self): + await self._invoke_method_async("open_view") + + def close_view(self, text: Optional[str] = None): + asyncio.create_task(self.close_view_async(text)) + + async def close_view_async(self, text: Optional[str] = None): + await self._invoke_method_async( + "close_view", {"text": text if text is not None else self.value} + ) diff --git a/sdk/python/packages/flet/src/flet/controls/material/segmented_button.py b/sdk/python/packages/flet/src/flet/controls/material/segmented_button.py new file mode 100644 index 000000000..4d4fda34f --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/segmented_button.py @@ -0,0 +1,161 @@ +from dataclasses import field +from typing import Optional + +from flet.controls.alignment import Axis +from flet.controls.base_control import control +from flet.controls.buttons import ButtonStyle +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.padding import OptionalPaddingValue +from flet.controls.types import ( + IconValue, + IconValueOrControl, + StrOrControl, +) + +__all__ = ["SegmentedButton", "Segment"] + + +@control("Segment") +class Segment(Control): + value: str + """ + Used to identify the `Segment`. + """ + + icon: Optional[IconValueOrControl] = None + """ + The icon (typically an [`Icon`](https://flet.dev/docs/controls/icon)) to be + displayed in the segment. + """ + + label: Optional[StrOrControl] = None + """ + The label (usually a [`Text`](https://flet.dev/docs/controls/text)) to be + displayed in the segment. + """ + + def before_update(self): + super().before_update() + assert ( + (isinstance(self.icon, IconValue)) + or (isinstance(self.icon, Control) and self.icon.visible) + or (isinstance(self.label, str)) + or (isinstance(self.label, Control) and self.label.visible) + ), "one of icon or label must be set and visible" + + +@control("SegmentedButton") +class SegmentedButton(ConstrainedControl): + """ + A segmented button control. + + Online docs: https://flet.dev/docs/controls/segmentedbutton + """ + + segments: list[Segment] + """ + A required parameter that describes the segments in the button. It's a list of + `Segment` objects. + """ + + style: Optional[ButtonStyle] = None + """ + Customizes this button's appearance. + + Value is of type [`ButtonStyle`](https://flet.dev/docs/reference/types/buttonstyle). + """ + + allow_empty_selection: bool = False + """ + A boolean value that indicates if having no selected segments is allowed. + + If `True`, then it is acceptable for none of the segments to be selected and also + that `selected` can be empty. + + If `False` (the default), there must be at least one segment selected. If the user + taps on the only selected segment it will not be deselected, and `on_change` will + not be called. + """ + + allow_multiple_selection: bool = False + """ + A boolean value that indicates if multiple segments can be selected at one time. + + If `True`, more than one segment can be selected. When selecting a segment, the + other `selected` segments will stay selected. Selecting an already selected segment + will unselect it. + + If `False`(the default), only one segment may be selected at a time. When a segment + is selected, any previously selected segment will be unselected. + """ + + selected: list[str] = field(default_factory=list) + """ + A set of `Segment.value`s that indicate which segments are selected. It is updated + when the user (un)selects a segment. + """ + + selected_icon: Optional[Control] = None + """ + An `Icon` control that is used to indicate a segment is selected. + + If `show_selected_icon` is `True` then for `selected` segments this icon will be + shown before the `Segment.label`, replacing the `Segment.icon` if it is specified. + + Defaults to an `Icon` with the `CHECK` icon. + """ + + show_selected_icon: bool = True + """ + A boolean value that indicates if the `selected_icon` is displayed on the + `selected` segments. + + If `True`, the `selected_icon` will be displayed at the start of the `selected` + segments. + + If `False`, then the `selected_icon` is not used and will not be displayed on + `selected` segments. + """ + + direction: Optional[Axis] = None + """ + The orientation of the button's `segments`. + + Value is of type [`Axis`](https://flet.dev/docs/reference/types/axis) and defaults + to `Axis.HORIZONTAL`. + """ + + padding: OptionalPaddingValue = None + """ + Defines the button's size and padding. If specified, the button expands to fill its + parent's space with this padding. + + When `None`, the button adopts its intrinsic content size. + + Value is of type + [`PaddingValue`](https://flet.dev/docs/reference/types/aliases#paddingvalue). + """ + + on_change: OptionalControlEventHandler["SegmentedButton"] = None + """ + Fires when the selection changes. + """ + + def before_update(self): + super().before_update() + assert any(segment.visible for segment in self.segments), ( + "segments must have at minimum one visible Segment" + ) + assert len(self.selected) > 0 or self.allow_empty_selection, ( + "allow_empty_selection must be True for selected to be empty" + ) + assert len(self.selected) < 2 or self.allow_multiple_selection, ( + "allow_multiple_selection must be True for selected to have more than one " + ) + "item" + assert len(self.selected) < 2 or self.allow_multiple_selection, ( + "allow_multiple_selection must be True for selected to have more than one " + ) + "item" diff --git a/sdk/python/packages/flet/src/flet/controls/material/selection_area.py b/sdk/python/packages/flet/src/flet/controls/material/selection_area.py new file mode 100644 index 000000000..441692fbc --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/selection_area.py @@ -0,0 +1,31 @@ +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler + +__all__ = ["SelectionArea"] + + +@control("SelectionArea") +class SelectionArea(Control): + """ + Flet controls are not selectable by default. SelectionArea is used to enable + selection for its child control. + + Online docs: https://flet.dev/docs/controls/selectionarea + """ + + content: Control + """ + A child Control contained by the SelectionArea. If you need to have multiple + selectable controls, use `Row`, `Column`, or `Stack`, which have a `controls` + property, and then provide multiple controls to that control. + """ + + on_change: OptionalControlEventHandler["SelectionArea"] = None + """ + Fires when the selected content changes. + """ + + def before_update(self): + super().before_update() + assert self.content.visible, "content must be visible" diff --git a/sdk/python/packages/flet/src/flet/controls/material/slider.py b/sdk/python/packages/flet/src/flet/controls/material/slider.py new file mode 100644 index 000000000..b6ca4a804 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/slider.py @@ -0,0 +1,215 @@ +from enum import Enum +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.control_state import ControlStateValue +from flet.controls.padding import PaddingValue +from flet.controls.types import ( + ColorValue, + MouseCursor, + Number, + OptionalColorValue, + OptionalNumber, +) + +__all__ = ["Slider", "SliderInteraction"] + + +class SliderInteraction(Enum): + TAP_AND_SLIDE = "tapAndSlide" + TAP_ONLY = "tapOnly" + SLIDE_ONLY = "slideOnly" + SLIDE_THUMB = "slideThumb" + + +@control("Slider") +class Slider(ConstrainedControl, AdaptiveControl): + """ + A slider provides a visual indication of adjustable content, as well as the + current setting in the total range of content. + + Use a slider when you want people to set defined values (such as volume or + brightness), or when people would benefit from instant feedback on the effect + of setting changes. + + Online docs: https://flet.dev/docs/controls/slider + """ + + value: OptionalNumber = None + """ + The currently selected value for this slider. + + The slider's thumb is drawn at a position that corresponds to this value. + + Defaults to value of `min` property. + """ + + label: Optional[str] = None + """ + Format with `{value}`. + + A label to show above the slider when the slider is active. The value of + `label` may contain `{value}` which will be replaced with a current slider + value. + + It is used to display the value of a discrete slider, and it is displayed as + part of the value indicator shape. + + If not set, then the value indicator will not be displayed. + """ + + min: Number = 0.0 + """ + The minimum value the user can select. Must be less than or equal to `max`. + + If the `max` is equal to the `min`, then the slider is disabled. + + Defaults to `0.0`. + """ + + max: Number = 1.0 + """ + The maximum value the user can select. Must be greater than or equal to `min`. + + If the `max` is equal to the `min`, then the slider is disabled. + + Defaults to `1.0`. + """ + + divisions: Optional[int] = None + """ + The number of discrete divisions. + + Typically used with `label` to show the current discrete value. + + If not set, the slider is continuous. + """ + + round: int = 0 + """ + The number of decimals displayed on the `label` containing `value`. + + Defaults to `0`, which displays value rounded to the nearest integer. + """ + + autofocus: bool = False + """ + True if the control will be selected as the initial focus. If there is more + than one control on a page with autofocus set, then the first one added to the + page will get focus. + """ + + active_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use for the portion of + the slider track that is active. + + The "active" side of the slider is the side between the thumb and the minimum + value. + """ + + inactive_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) for the inactive portion of + the slider track. + + The "inactive" side of the slider is the side between the thumb and the maximum + value. + """ + + thumb_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the thumb. + """ + + interaction: Optional[SliderInteraction] = None + """ + The allowed way for the user to interact with this slider. Value is a + [`SliderInteraction`](https://flet.dev/docs/reference/types/sliderinteraction) + and defaults to `SliderInteraction.TAP_AND_SLIDE`. + """ + + secondary_active_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use for the portion of + the slider track between the thumb and the `secondary_track_value`. + """ + + overlay_color: Optional[ControlStateValue[ColorValue]] = None + """ + The highlight [color](https://flet.dev/docs/reference/colors) that's typically + used to indicate that the range slider thumb is in `ControlState.HOVERED` or + `DRAGGED` + [`ControlState`](https://flet.dev/docs/reference/types/controlstate)s. + """ + + secondary_track_value: OptionalNumber = None + """ + The secondary track value for this slider. + + If not null, a secondary track using `secondary_active_color` is drawn between + the thumb and this value, over the inactive track. If less than `value`, then + the secondary track is not shown. + + It can be ideal for media scenarios such as showing the buffering progress + while the `value` shows the play progress. + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + The cursor to be displayed when a mouse pointer enters or is hovering over this + control. + + Value is of type + [`MouseCursor`](https://flet.dev/docs/reference/types/mousecursor). + """ + + padding: Optional[PaddingValue] = None + """ + TBD + """ + + year_2023: Optional[bool] = None + """ + TBD + """ + + on_change: OptionalControlEventHandler["Slider"] = None + """ + Fires when the state of the Slider is changed. + """ + + on_change_start: OptionalControlEventHandler["Slider"] = None + """ + Fires when the user starts selecting a new value for the slider. + """ + + on_change_end: OptionalControlEventHandler["Slider"] = None + """ + Fires when the user is done selecting a new value for the slider. + """ + + on_focus: OptionalControlEventHandler["Slider"] = None + """ + Fires when the control has received focus. + """ + + on_blur: OptionalControlEventHandler["Slider"] = None + """ + Fires when the control has lost focus. + """ + + def before_update(self): + super().before_update() + assert self.max is None or self.min <= self.max, ( + "min must be less than or equal to max" + ) + assert self.value is None or self.value >= self.min, ( + "value must be greater than or equal to min" + ) + assert self.value is None or self.value <= self.max, ( + "value must be less than or equal to max" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/material/snack_bar.py b/sdk/python/packages/flet/src/flet/controls/material/snack_bar.py new file mode 100644 index 000000000..9b5c499f7 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/snack_bar.py @@ -0,0 +1,245 @@ +from dataclasses import field +from enum import Enum +from typing import Optional, Union + +from flet.controls.base_control import control +from flet.controls.buttons import OutlinedBorder +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.dialog_control import DialogControl +from flet.controls.duration import Duration, DurationValue +from flet.controls.margin import OptionalMarginValue +from flet.controls.padding import OptionalPaddingValue +from flet.controls.types import ( + ClipBehavior, + Number, + OptionalColorValue, + OptionalNumber, + StrOrControl, +) + +__all__ = ["SnackBar", "SnackBarBehavior", "DismissDirection", "SnackBarAction"] + + +class SnackBarBehavior(Enum): + FIXED = "fixed" + FLOATING = "floating" + + +class DismissDirection(Enum): + NONE = "none" + VERTICAL = "vertical" + HORIZONTAL = "horizontal" + END_TO_START = "endToStart" + START_TO_END = "startToEnd" + UP = "up" + DOWN = "down" + + +@control("SnackBar") +class SnackBarAction(Control): + """A button that can be used as an action in a `SnackBar`.""" + + label: str + """ + The button's label. + """ + + text_color: OptionalColorValue = None + """ + The button label color. + If not provided, defaults to `SnackBarTheme.action_text_color`. + """ + + disabled_text_color: OptionalColorValue = None + """ + The button disabled label color. + This color is shown after the action is dismissed. + """ + + bgcolor: OptionalColorValue = None + """ + The button background fill color. + If not provided, defaults to `SnackBarTheme.action_bgcolor`. + """ + + disabled_bgcolor: OptionalColorValue = None + """ + The button disabled background color. + This color is shown after the action is dismissed. + + If not provided, defaults to `SnackBarTheme.disabled_action_bgcolor`. + """ + + on_click: OptionalControlEventHandler["SnackBarAction"] = None + """ + Fires when this action button is clicked. + """ + + +@control("SnackBar") +class SnackBar(DialogControl): + """ + A lightweight message with an optional action which briefly displays at the + bottom of the screen. + + Online docs: https://flet.dev/docs/controls/snackbar + """ + + content: StrOrControl + """ + The primary content of the snack bar. + + Typically a [`Text`](https://flet.dev/docs/controls/text) control. + """ + + behavior: Optional[SnackBarBehavior] = None + """ + This defines the behavior and location of the snack bar. + + Defines where a SnackBar should appear within a page and how its location + should be adjusted when the page also includes a `FloatingActionButton` or a + `NavigationBar`. + + Value is of type + [`SnackBarBehavior`](https://flet.dev/docs/reference/types/snackbarbehavior) + and defaults to `SnackBarBehavior.FIXED`. + + **Note:** + + * If `behavior=SnackBarBehavior.FLOATING`, the length of the bar is defined + by either `width` or `margin`, and if both are specified, `width` takes + precedence over `margin`. + * `width` and `margin` are ignored if `behavior!=SnackBarBehavior.FLOATING`. + """ + + dismiss_direction: Optional[DismissDirection] = None + """ + The direction in which the SnackBar can be dismissed. + + Value is of type + [`DismissDirection`](https://flet.dev/docs/reference/types/dismissdirection) + and defaults to `DismissDirection.DOWN`. + """ + + show_close_icon: bool = False + """ + Whether to include a "close" icon widget. + + Tapping the icon will close the snack bar. + """ + + action: Union[str, SnackBarAction, None] = None + """ + An optional action that the user can take based on the snack bar. + + For example, the snack bar might let the user undo the operation that prompted + the snackbar. Snack bars can have at most one action. + + The action should not be "dismiss" or "cancel". + """ + + close_icon_color: OptionalColorValue = None + """ + An optional color for the close icon, if `show_close_icon=True`. + """ + + bgcolor: OptionalColorValue = None + """ + SnackBar background [color](https://flet.dev/docs/reference/colors). + """ + + duration: DurationValue = field(default_factory=lambda: Duration(milliseconds=4000)) + """ + The number of *milliseconds* that the SnackBar stays open for. + + Defaults to `4000` + ([4 seconds](https://api.flutter.dev/flutter/material/SnackBar/duration.html)). + """ + + margin: OptionalMarginValue = None + """ + Empty space to surround the snack bar. + + Has effect only when `behavior=SnackBarBehavior.FLOATING` and will be ignored + if `width` is specified. + """ + + padding: OptionalPaddingValue = None + """ + The amount of padding to apply to the snack bar's content and optional action. + + Value is of type + [`Padding`](https://flet.dev/docs/reference/types/padding) or a number. + """ + + width: OptionalNumber = None + """ + The width of the snack bar. + + If width is specified, the snack bar will be centered horizontally in the + available space. + + Has effect only when `behavior=SnackBarBehavior.FLOATING`. It can not be used + if `margin` is specified. + """ + + elevation: OptionalNumber = None + """ + The z-coordinate at which to place the snack bar. This controls the size of the + shadow below the snack bar. + """ + + shape: Optional[OutlinedBorder] = None + """ + The shape of the snack bar's. + + Value is of type + [`OutlinedBorder`](https://flet.dev/docs/reference/types/outlinedborder). + """ + + clip_behavior: ClipBehavior = ClipBehavior.HARD_EDGE + """ + The `content` will be clipped (or not) according to this option. + + Value is of type + [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) and + defaults to `ClipBehavior.HARD_EDGE`. + """ + + action_overflow_threshold: Number = 0.25 + """ + The percentage (between `0.0` and `1.0`) threshold for `action`'s width before + it overflows to a new line. + + If the width of the snackbar's `content` is greater than this percentage of the + width of the snackbar minus the width of its `action`, then the `action` will + appear below the `content`. + + At a value of `0.0`, the `action` will not overflow to a new line. + + Defaults to `0.25`. + """ + + on_action: OptionalControlEventHandler["SnackBar"] = None + """ + Fires when action button is clicked. + """ + + on_visible: OptionalControlEventHandler["SnackBar"] = None + """ + Fires the first time that the snackbar is visible within the page. + """ + + def before_update(self): + super().before_update() + assert isinstance(self.content, str) or ( + isinstance(self.content, Control) and self.content.visible + ), "content must be a string or a visible control" + assert ( + self.action_overflow_threshold is None + or 0 <= self.action_overflow_threshold <= 1 + ), "action_overflow_threshold must be between 0 and 1 inclusive" + assert self.elevation is None or self.elevation >= 0, ( + "elevation cannot be negative" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/material/submenu_button.py b/sdk/python/packages/flet/src/flet/controls/material/submenu_button.py new file mode 100644 index 000000000..fbaef5a86 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/submenu_button.py @@ -0,0 +1,109 @@ +from dataclasses import field +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.buttons import ButtonStyle +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.material.menu_bar import MenuStyle +from flet.controls.transform import OffsetValue +from flet.controls.types import ClipBehavior + +__all__ = ["SubmenuButton"] + + +@control("SubmenuButton") +class SubmenuButton(ConstrainedControl): + """ + A menu button that displays a cascading menu. It can be used as part of + a MenuBar, or as a standalone control. + + Online docs: https://flet.dev/docs/controls/submenubutton + """ + + content: Optional[Control] = None + """ + The child control to be displayed in the middle portion of this button. + + Typically this is the button's label, using a `Text` control. + """ + + controls: list[Control] = field(default_factory=list) + """ + A list of controls that appear in the menu when it is opened. + + Typically either `MenuItemButton` or `SubMenuButton` controls. + + If this list is empty, then the button for this menu item will be disabled. + """ + + leading: Optional[Control] = None + """ + An optional control to display before the `content`. + + Typically an [`Icon`](https://flet.dev/docs/controls/icon) control. + """ + + trailing: Optional[Control] = None + """ + An optional control to display after the `content`. + + Typically an [`Icon`](https://flet.dev/docs/controls/icon) control. + """ + + clip_behavior: ClipBehavior = ClipBehavior.HARD_EDGE + """ + Whether to clip the content of this control or not. + + Value is of type + [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) and + defaults to `ClipBehavior.HARD_EDGE`. + """ + + menu_style: Optional[MenuStyle] = None + """ + Customizes this menu's appearance. + + Value is of type + [`MenuStyle`](https://flet.dev/docs/reference/types/menustyle). + """ + + style: Optional[ButtonStyle] = None + """ + Customizes this button's appearance. + + Value is of type + [`ButtonStyle`](https://flet.dev/docs/reference/types/buttonstyle). + """ + + alignment_offset: Optional[OffsetValue] = None + """ + The offset of the menu relative to the alignment origin determined by + `MenuStyle.alignment` on the `style` attribute. + """ + + on_open: OptionalControlEventHandler["SubmenuButton"] = None + """ + Fired when the menu is opened. + """ + + on_close: OptionalControlEventHandler["SubmenuButton"] = None + """ + Fired when the menu is closed. + """ + + on_hover: OptionalControlEventHandler["SubmenuButton"] = None + """ + Fired when the button is hovered. + """ + + on_focus: OptionalControlEventHandler["SubmenuButton"] = None + """ + Fired when the button receives focus. + """ + + on_blur: OptionalControlEventHandler["SubmenuButton"] = None + """ + Fired when this button loses focus. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/switch.py b/sdk/python/packages/flet/src/flet/controls/material/switch.py new file mode 100644 index 000000000..bddb2f1e8 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/switch.py @@ -0,0 +1,229 @@ +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.control_state import ControlStateValue +from flet.controls.text_style import TextStyle +from flet.controls.types import ( + ColorValue, + IconValue, + LabelPosition, + MouseCursor, + OptionalColorValue, + OptionalNumber, + StrOrControl, +) + +__all__ = ["Switch"] + + +@control("Switch") +class Switch(ConstrainedControl, AdaptiveControl): + """ + A toggle represents a physical switch that allows someone to choose between + two mutually exclusive options. + + For example, "On/Off", "Show/Hide". Choosing an option should produce + an immediate result. + + Online docs: https://flet.dev/docs/controls/switch + """ + + label: Optional[StrOrControl] = None + """ + The clickable label to display on the right of the Switch. + """ + + label_position: Optional[LabelPosition] = None + """ + Value is of type + [`LabelPosition`](https://flet.dev/docs/reference/types/labelposition) and + defaults to `LabelPosition.RIGHT`. + """ + + label_style: Optional[TextStyle] = None + """ + The label's style. + + Value is of type + [`TextStyle`](https://flet.dev/docs/reference/types/textstyle). + """ + + value: bool = False + """ + Current value of the Switch. + """ + + autofocus: bool = False + """ + True if the control will be selected as the initial focus. If there is more + than one control on a page with autofocus set, then the first one added to + the page will get focus. + """ + + active_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use when this switch + is on. + """ + + active_track_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use on the track when + this switch is on. + + If `track_color` returns a non-null color in the `SELECTED` state, it will + be used instead of this color. + """ + + focus_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use for the focus + highlight for keyboard interactions. + """ + + inactive_thumb_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use on the thumb when + this switch is off. + + Defaults to colors defined in the + [material design specification](https://m3.material.io/components/switch/specs). + + If `thumb_color` returns a non-null color in the `DEFAULT` state, it will be + used instead of this color. + """ + + inactive_track_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use on the track when + this switch is off. + + Defaults to colors defined in the + [material design specification](https://m3.material.io/components/switch/specs). + + If `track_color` returns a non-null color in the `DEFAULT` state, it will be + used instead of this color. + """ + + thumb_color: Optional[ControlStateValue[ColorValue]] = None + """ + The [color](https://flet.dev/docs/reference/colors) of this switch's thumb + in various [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + states. + + The following [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + values are supported: `SELECTED`, `HOVERED`, `DISABLED`, `FOCUSED` and + `DEFAULT` (fallback). + """ + + thumb_icon: Optional[ControlStateValue[IconValue]] = None + """ + The icon of this Switch's thumb in various + [`ControlState`](https://flet.dev/docs/reference/types/controlstate) states. + + The following [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + values are supported: `SELECTED`, `HOVERED`, `DISABLED`, `FOCUSED` and + `DEFAULT` (fallback). + """ + + track_color: Optional[ControlStateValue[ColorValue]] = None + """ + The [color](https://flet.dev/docs/reference/colors) of this switch's track + in various [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + states. + + The following [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + values are supported: `SELECTED`, `HOVERED`, `DISABLED`, `FOCUSED` and + `DEFAULT` (fallback). + """ + + adaptive: Optional[bool] = None + """ + If the value is `True`, an adaptive Switch is created based on whether the + target platform is iOS/macOS. + + On iOS and macOS, a `CupertinoSwitch` is created, which has matching + functionality and presentation as `Switch`, and the graphics as expected on + iOS. On other platforms, a Material Switch is created. + + Defaults to `False`. See the example of usage + [here](https://flet.dev/docs/controls/cupertinoswitch#cupertinoswitch-and-adaptive-switch). + """ + + hover_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to be used when it is + being hovered over by the mouse pointer. + """ + + splash_radius: OptionalNumber = None + """ + The radius of the splash effect when the switch is pressed. + """ + + overlay_color: Optional[ControlStateValue[ColorValue]] = None + """ + The [color](https://flet.dev/docs/reference/colors) for the switch's + Material in various + [`ControlState`](https://flet.dev/docs/reference/types/controlstate) states. + + The following [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + values are supported: `PRESSED`, `SELECTED`, `HOVERED`, `FOCUSED` and + `DEFAULT`. + """ + + track_outline_color: Optional[ControlStateValue[ColorValue]] = None + """ + The outline [color](https://flet.dev/docs/reference/colors) of this switch's + track in various [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + states. + + The following [`ControlState`](https://flet.dev/docs/reference/types/controlstate) + values are supported: `SELECTED`, `HOVERED`, `DISABLED`, `FOCUSED` and + `DEFAULT` (fallback). + """ + + track_outline_width: Optional[ControlStateValue[OptionalNumber]] = None + """ + The outline width of this switch's track in all or specific + [`ControlState`](https://flet.dev/docs/reference/types/controlstate) states. + + The following states are supported: `SELECTED`, `HOVERED`, `DISABLED`, + `FOCUSED` and `DEFAULT` (fallback). + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + The cursor to be displayed when a mouse pointer enters or is hovering over + this control. + + The value is + [`MouseCursor`](https://flet.dev/docs/reference/types/mousecursor) enum. + """ + + on_change: OptionalControlEventHandler["Switch"] = None + """ + Fires when the state of the Switch is changed. + """ + + on_focus: OptionalControlEventHandler["Switch"] = None + """ + Fires when the control has received focus. + + Event handler argument is of type + [`OnFocusEvent`](https://flet.dev/docs/reference/types/onfocusevent). + """ + + on_blur: OptionalControlEventHandler["Switch"] = None + """ + Fires when the control has lost focus. + """ + + def before_update(self): + super().before_update() + assert self.splash_radius is None or self.splash_radius >= 0, ( + "splash_radius cannot be negative" + ) diff --git a/sdk/python/packages/flet/src/flet/controls/material/tabs.py b/sdk/python/packages/flet/src/flet/controls/material/tabs.py new file mode 100644 index 000000000..6c8800c9e --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/tabs.py @@ -0,0 +1,265 @@ +from dataclasses import field +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.border import BorderSide +from flet.controls.border_radius import OptionalBorderRadiusValue +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control import Control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.control_state import ControlStateValue +from flet.controls.duration import OptionalDurationValue +from flet.controls.margin import OptionalMarginValue +from flet.controls.material.form_field_control import IconValueOrControl +from flet.controls.padding import OptionalPaddingValue, PaddingValue +from flet.controls.text_style import OptionalTextStyle +from flet.controls.types import ( + ClipBehavior, + ColorValue, + MouseCursor, + Number, + OptionalColorValue, + OptionalNumber, + StrOrControl, + TabAlignment, +) + +__all__ = ["Tab", "Tabs"] + + +@control("Tab") +class Tab(AdaptiveControl): + """ + TBD + """ + + label: Optional[StrOrControl] = None + """ + String or Control to display as Tab's name. + """ + + content: Optional[Control] = None + """ + A `Control` to display below the Tab when it is selected. + """ + + icon: Optional[IconValueOrControl] = None + """ + An icon to display on the left of Tab text. + """ + + height: OptionalNumber = None + """ + TBD + """ + + icon_margin: OptionalMarginValue = None + """ + TBD + """ + + def before_update(self): + super().before_update() + assert (self.label is not None) or (self.icon is not None), ( + "Tab must have at least label or icon property set" + ) + + +@control("Tabs") +class Tabs(ConstrainedControl, AdaptiveControl): + """ + The Tabs control is used for navigating frequently accessed, distinct content + categories. Tabs allow for navigation between two or more content views and relies + on text headers to articulate the different sections of content. + + Online docs: https://flet.dev/docs/controls/tabs + """ + + tabs: list[Tab] = field(default_factory=list) + """ + A list of `Tab` controls. + """ + + selected_index: int = 0 + """ + The index of currently selected tab. + """ + + scrollable: bool = True + """ + Whether this tab bar can be scrolled horizontally. + + If `scrollable` is `True`, then each tab is as wide as needed for its label and + the entire Tabs controls is scrollable. Otherwise each tab gets an equal share + of the available space. + """ + + tab_alignment: Optional[TabAlignment] = None + """ + Specifies the horizontal alignment of the tabs within the Tabs control. + + Value is of type [`TabAlignment`](https://flet.dev/docs/reference/types/tabalignment) + and defaults to `TabAlignment.START`, if `scrollable=True`, and to + `TabAlignment.FILL`, if `scrollable=False`. + """ + + animation_duration: OptionalDurationValue = None + """ + Duration of animation in milliseconds of switching between tabs. + + Defaults to `50`. + """ + + divider_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the divider. + """ + + indicator_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of the indicator(line that + appears below the selected tab). + """ + + indicator_border_radius: OptionalBorderRadiusValue = None + """ + The radius of the indicator's corners. + """ + + indicator_border_side: Optional[BorderSide] = None + """ + The [color](https://flet.dev/docs/reference/colors) and weight of the horizontal + line drawn below the selected tab. + """ + + indicator_padding: PaddingValue = 0 + """ + Locates the selected tab's underline relative to the tab's boundary. + + The `indicator_tab_size` property can be used to define the tab indicator's bounds + in terms of its (centered) tab widget with `False`, or the entire tab with `True`. + """ + + indicator_tab_size: Optional[bool] = None + """ + `True` for indicator to take entire tab. + """ + + is_secondary: Optional[bool] = None + """ + Whether to create a secondary/nested tab bar. + + Secondary tabs are used within a content area to further separate related content + and establish hierarchy. + + Defaults to `False`. + """ + + label_color: bool = False + """ + The [color](https://flet.dev/docs/reference/colors) of selected tab labels. + """ + + label_padding: OptionalPaddingValue = None + """ + The padding around the tab label. + + Value is of type [`Padding`](https://flet.dev/docs/reference/types/padding). + """ + + label_text_style: OptionalTextStyle = None + """ + The text style of the tab labels. + + Value is of type [`TextStyle`](https://flet.dev/docs/reference/types/textstyle). + """ + + unselected_label_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of unselected tab labels. + """ + + unselected_label_text_style: OptionalTextStyle = None + """ + The text style of the unselected tab labels. + + Value is of type [`TextStyle`](https://flet.dev/docs/reference/types/textstyle). + """ + + overlay_color: Optional[ControlStateValue[ColorValue]] = None + """ + Defines the ink response focus, hover, and splash + [colors](https://flet.dev/docs/reference/colors) in various + [`ControlState`](https://flet.dev/docs/reference/types/controlstate) states. + + The following `ControlState` values are supported: `PRESSED`, `HOVERED` and + `FOCUSED`. + """ + + divider_height: OptionalNumber = None + """ + The height of the divider. + + Defaults to `1.0`. + """ + + indicator_thickness: Number = 2.0 + """ + The thickness of the indicator. Value must be greater than zero. + + Defaults to `2.0`. + """ + + enable_feedback: Optional[str] = None + """ + Whether detected gestures should provide acoustic and/or haptic feedback. + + On Android, for example, setting this to `True` produce a click sound and a + long-press will produce a short vibration. + + Defaults to `True`. + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + The cursor to be displayed when a mouse pointer enters or is hovering over this + control. + + The value is [`MouseCursor`](https://flet.dev/docs/reference/types/mousecursor) + enum. + """ + + padding: OptionalPaddingValue = None + """ + The padding around the Tabs control. + + Value is of type [`Padding`](https://flet.dev/docs/reference/types/padding). + """ + + splash_border_radius: OptionalBorderRadiusValue = None + """ + Defines the clipping radius of splashes that extend outside the bounds of the tab. + + Value is of type [`BorderRadius`](https://flet.dev/docs/reference/types/borderradius). + """ + + clip_behavior: ClipBehavior = ClipBehavior.HARD_EDGE + """ + The content will be clipped (or not) according to this option. + + Value is of type [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior). + """ + + on_click: OptionalControlEventHandler["Tabs"] = None + """ + Fires when a tab is clicked. + """ + + on_change: OptionalControlEventHandler["Tabs"] = None + """ + Fires when `selected_index` changes. + """ + + def __contains__(self, item): + return item in self.tabs diff --git a/sdk/python/packages/flet/src/flet/controls/material/text_button.py b/sdk/python/packages/flet/src/flet/controls/material/text_button.py new file mode 100644 index 000000000..4027aaead --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/text_button.py @@ -0,0 +1,112 @@ +import asyncio +from typing import Optional + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.buttons import ButtonStyle +from flet.controls.constrained_control import ConstrainedControl +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.types import ( + ClipBehavior, + IconValueOrControl, + OptionalColorValue, + StrOrControl, + UrlTarget, +) + +__all__ = ["TextButton"] + + +@control("TextButton") +class TextButton(ConstrainedControl, AdaptiveControl): + """ + Text buttons are used for the lowest priority actions, especially when presenting + multiple options. Text buttons can be placed on a variety of backgrounds. Until the + button is interacted with, its container isn’t visible. + + Online docs: https://flet.dev/docs/controls/textbutton + """ + + content: Optional[StrOrControl] = None + """ + A Control representing custom button content. + """ + + icon: Optional[IconValueOrControl] = None + """ + Icon shown in the button. + """ + + icon_color: OptionalColorValue = None + """ + Icon [color](https://flet.dev/docs/reference/colors). + """ + + style: Optional[ButtonStyle] = None + """ + Value is of type [`ButtonStyle`](https://flet.dev/docs/reference/types/buttonstyle). + """ + + autofocus: bool = False + """ + True if the control will be selected as the initial focus. + + If there is more than one control on a page with autofocus set, then the first one + added to the page will get focus. + """ + + url: Optional[str] = None + """ + The URL to open when the button is clicked. + + If registered, `on_click` event is fired after that. + """ + + url_target: Optional[UrlTarget] = None + """ + Where to open URL in the web mode. + + Value is of type [`UrlTarget`](https://flet.dev/docs/reference/types/urltarget). + """ + + clip_behavior: ClipBehavior = ClipBehavior.NONE + """ + The content will be clipped (or not) according to this option. + + Value is of type [`ClipBehavior`](https://flet.dev/docs/reference/types/clipbehavior) + and defaults to `ClipBehavior.NONE`. + """ + + on_click: OptionalControlEventHandler["TextButton"] = None + """ + Fires when a user clicks the button. + """ + + on_long_press: OptionalControlEventHandler["TextButton"] = None + """ + Fires when the button is long-pressed. + """ + + on_hover: OptionalControlEventHandler["TextButton"] = None + """ + Fires when a mouse pointer enters or exists the button response area. + + `data` property of event object contains `true` (string) when cursor enters and + `false` when it exits. + """ + + on_focus: OptionalControlEventHandler["TextButton"] = None + """ + Fires when the control has received focus. + """ + + on_blur: OptionalControlEventHandler["TextButton"] = None + """ + Fires when the control has lost focus. + """ + + async def focus_async(self): + await self._invoke_method_async("focus") + + def focus(self): + asyncio.create_task(self.focus_async()) diff --git a/sdk/python/packages/flet/src/flet/controls/material/textfield.py b/sdk/python/packages/flet/src/flet/controls/material/textfield.py new file mode 100644 index 000000000..8ed65422d --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/textfield.py @@ -0,0 +1,457 @@ +from dataclasses import dataclass +from enum import Enum +from typing import Optional, Union + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.core.autofill_group import AutofillHint +from flet.controls.material.form_field_control import FormFieldControl +from flet.controls.padding import PaddingValue +from flet.controls.text_style import StrutStyle +from flet.controls.types import ( + Brightness, + ClipBehavior, + MouseCursor, + Number, + OptionalColorValue, + OptionalNumber, + TextAlign, +) + +__all__ = [ + "TextField", + "KeyboardType", + "TextCapitalization", + "InputFilter", + "NumbersOnlyInputFilter", + "TextOnlyInputFilter", +] + + +class KeyboardType(Enum): + NONE = "none" + TEXT = "text" + MULTILINE = "multiline" + NUMBER = "number" + PHONE = "phone" + DATETIME = "datetime" + EMAIL = "email" + URL = "url" + VISIBLE_PASSWORD = "visiblePassword" + NAME = "name" + STREET_ADDRESS = "streetAddress" + + +class TextCapitalization(Enum): + CHARACTERS = "characters" + WORDS = "words" + SENTENCES = "sentences" + + +@dataclass +class InputFilter: + """ + `InputFilter` class. + """ + + regex_string: str + """ + A regular expression pattern for the filter. + + It is recommended to use raw strings (prefix your string with `r`) for the pattern, + ex: `r"pattern"`. + """ + + allow: bool = True + """ + A boolean value indicating whether to allow or deny/block the matched patterns. + + Value is of type `bool` and defaults to `True`. + """ + + replacement_string: str = "" + """ + A string used to replace banned/denied patterns. + + Defaults to an empty string. + """ + + multiline: bool = False + """ + Whether this regular expression matches multiple lines. + + If the regexp does match multiple lines, the "^" and "$" characters match the + beginning and end of lines. If not, the characters match the beginning and end of + the input. + """ + + case_sensitive: bool = True + """ + Whether this regular expression is case sensitive. + + If the regular expression is not case sensitive, it will match an input letter with + a pattern letter even if the two letters are different case versions of the same + letter. + """ + + unicode: bool = False + """ + Whether this regular expression uses Unicode mode. + """ + + dot_all: bool = False + """ + Whether "." in this regular expression matches line terminators. + + When false, the "." character matches a single character, unless that character + terminates a line. When true, then the "." character will match any single + character including line terminators. + + This feature is distinct from `multiline`. They affect the behavior of different + pattern characters, so they can be used together or separately. + """ + + +class NumbersOnlyInputFilter(InputFilter): + """ + Allows only numbers. + """ + + def __init__(self): + super().__init__(regex_string=r"^[0-9]*$", allow=True, replacement_string="") + + +class TextOnlyInputFilter(InputFilter): + """ + Allows only text. + """ + + def __init__(self): + super().__init__(regex_string=r"^[a-zA-Z]*$", allow=True, replacement_string="") + + +@control("TextField") +class TextField(FormFieldControl, AdaptiveControl): + """ + A text field lets the user enter text, either with hardware keyboard or with an + onscreen keyboard. + + Online docs: https://flet.dev/docs/controls/textfield + """ + + value: str = "" + """ + Current value of the TextField. + """ + + keyboard_type: KeyboardType = KeyboardType.TEXT + """ + The type of keyboard to use for editing the text. The property value is + [`KeyboardType`](https://flet.dev/docs/reference/types/keyboardtype) and defaults + to `KeyboardType.TEXT`. + """ + + multiline: bool = False + """ + `True` if TextField can contain multiple lines of text. + """ + + min_lines: Optional[int] = None + """ + The minimum number of lines to occupy when the content spans fewer lines. + + This affects the height of the field itself and does not limit the number of lines + that can be entered into the field. + + Defaults to `1`. + """ + + max_lines: Optional[int] = None + """ + The maximum number of lines to show at one time, wrapping if necessary. + + This affects the height of the field itself and does not limit the number of lines + that can be entered into the field. + + If this is `1` (the default), the text will not wrap, but will scroll horizontally + instead. + """ + + max_length: Optional[int] = None + """ + Limits a maximum number of characters that can be entered into TextField. + """ + + password: bool = False + """ + Whether to hide the text being edited. + + Defaults to `False`. + """ + + can_reveal_password: bool = False + """ + Displays a toggle icon button that allows revealing the entered password. Is shown + if both `password` and `can_reveal_password` are `True`. + + The icon is displayed in the same location as `suffix` and in case both + `can_reveal_password`/`password` and `suffix` are provided, then the `suffix` is + not shown. + """ + + read_only: bool = False + """ + Whether the text can be changed. + + When this is set to `True`, the text cannot be modified by any shortcut or keyboard + operation. The text is still selectable. + + Defaults to `False`. + """ + + shift_enter: bool = False + """ + Changes the behavior of `Enter` button in `multiline` TextField to be chat-like, + i.e. new line can be added with `Shift`+`Enter` and pressing just `Enter` fires + `on_submit` event. + """ + + text_align: Optional[TextAlign] = None + """ + How the text should be aligned horizontally. + + Value is of type [`TextAlign`](https://flet.dev/docs/reference/types/textalign) and + defaults to `TextAlign.LEFT`. + """ + + autofocus: bool = False + """ + True if the control will be selected as the initial focus. If there is more than + one control on a page with autofocus set, then the first one added to the page will + get focus. + """ + + capitalization: Optional[TextCapitalization] = None + """ + Enables automatic on-the-fly capitalization of entered text. + + Value is of type [`TextCapitalization`](https://flet.dev/docs/reference/types/textcapitalization) + and defaults to `TextCapitalization.NONE`. + """ + + autocorrect: bool = True + """ + Whether to enable autocorrection. + + Defaults to `True`. + """ + + enable_suggestions: bool = True + """ + Whether to show input suggestions as the user types. + + This flag only affects Android. On iOS, suggestions are tied directly to + `autocorrect`, so that suggestions are only shown when `autocorrect` is `True`. + On Android autocorrection and suggestion are controlled separately. + + Defaults to `True`. + """ + + smart_dashes_type: bool = True + """ + Whether to allow the platform to automatically format dashes. + + This flag only affects iOS versions 11 and above. As an example of what this does, + two consecutive hyphen characters will be automatically replaced with one en dash, + and three consecutive hyphens will become one em dash. + + Defaults to `True`. + """ + + smart_quotes_type: bool = True + """ + Whether to allow the platform to automatically format quotes. + + This flag only affects iOS. As an example of what this does, a standard vertical + double quote character will be automatically replaced by a left or right double + quote depending on its position in a word. + + Defaults to `True`. + """ + + show_cursor: bool = True + """ + Whether the field's cursor is to be shown. + + Defaults to `True`. + """ + + cursor_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of TextField cursor. + """ + + cursor_error_color: OptionalColorValue = None + """ + TBD + """ + + cursor_width: Number = 2.0 + """ + Sets cursor width. + """ + + cursor_height: OptionalNumber = None + """ + Sets cursor height. + """ + + cursor_radius: OptionalNumber = None + """ + Sets cursor radius. + """ + + selection_color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) of TextField selection. + """ + + input_filter: Optional[InputFilter] = None + """ + Provides as-you-type filtering/validation. + + Similar to the `on_change` callback, the input filters are not applied when the + content of the field is changed programmatically. + + Value is of type + [`InputFilter`](https://flet.dev/docs/reference/types/inputfilter) class. + """ + + obscuring_character: str = "•" + """ + TBD + """ + + enable_interactive_selection: bool = True + """ + TBD + """ + + enable_ime_personalized_learning: bool = True + """ + TBD + """ + + can_request_focus: bool = True + """ + TBD + """ + + ignore_pointers: bool = False + """ + TBD + """ + + enable_stylus_handwriting: bool = True + """ + TBD + """ + + animate_cursor_opacity: Optional[bool] = None + """ + TBD + """ + + always_call_on_tap: bool = False + """ + TBD + """ + + scroll_padding: PaddingValue = 20 + """ + TBD + """ + + clip_behavior: ClipBehavior = ClipBehavior.HARD_EDGE + """ + TBD + """ + + keyboard_brightness: Optional[Brightness] = None + """ + TBD + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + TBD + """ + + strut_style: Optional[StrutStyle] = None + """ + TBD + """ + + autofill_hints: Optional[Union[AutofillHint, list[AutofillHint]]] = None + """ + Helps the autofill service identify the type of this text input. Value can either + be a single [`AutoFillHint`](https://flet.dev/docs/reference/types/autofillhint) + enum item or a list of them. + + More information [here](https://api.flutter.dev/flutter/material/TextField/autofillHints.html). + """ + + on_change: OptionalControlEventHandler["TextField"] = None + """ + Fires when the typed input for the TextField has changed. + """ + + on_click: OptionalControlEventHandler["TextField"] = None + """ + TBD + """ + + on_submit: OptionalControlEventHandler["TextField"] = None + """ + Fires when user presses ENTER while focus is on TextField. + """ + + on_focus: OptionalControlEventHandler["TextField"] = None + """ + Fires when the control has received focus. + """ + + on_blur: OptionalControlEventHandler["TextField"] = None + """ + Fires when the control has lost focus. + """ + + on_tap_outside: OptionalControlEventHandler["TextField"] = None + """ + TBD + """ + + def before_update(self): + super().before_update() + assert self.min_lines is None or self.min_lines > 0, ( + "min_lines must be greater than 0" + ) + assert self.max_lines is None or self.max_lines > 0, ( + "min_lines must be greater than 0" + ) + assert ( + self.max_lines is None + or self.min_lines is None + or self.min_lines <= self.max_lines + ), "min_lines can't be greater than max_lines" + assert ( + self.max_length is None or self.max_length == -1 or self.max_length > 0 + ), "max_length must be either equal to -1 or greater than 0" + if ( + self.bgcolor is not None + or self.fill_color is not None + or self.hover_color is not None + or self.focused_color is not None + ) and self.filled is None: + self.filled = True # required to display any of the above colors diff --git a/sdk/python/packages/flet/src/flet/controls/material/time_picker.py b/sdk/python/packages/flet/src/flet/controls/material/time_picker.py new file mode 100644 index 000000000..787b1cdb5 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/time_picker.py @@ -0,0 +1,125 @@ +from dataclasses import field +from datetime import datetime, time +from enum import Enum +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import ( + Event, + OptionalControlEventHandler, + OptionalEventHandler, +) +from flet.controls.dialog_control import DialogControl +from flet.controls.types import ( + OptionalColorValue, + Orientation, +) + +__all__ = ["TimePicker", "TimePickerEntryMode", "TimePickerEntryModeChangeEvent"] + + +class TimePickerEntryMode(Enum): + DIAL = "dial" + INPUT = "input" + DIAL_ONLY = "dialOnly" + INPUT_ONLY = "inputOnly" + + +class TimePickerEntryModeChangeEvent(Event["TimePicker"]): + entry_mode: Optional[TimePickerEntryMode] + + +@control("TimePicker") +class TimePicker(DialogControl): + """ + A Material-style time picker dialog. + + Can be opened by calling `page.show_dialog()` method. + + Depending on the `time_picker_entry_mode`, it will show either a Dial or + an Input (hour and minute text fields) for picking a time. + + Online docs: https://flet.dev/docs/controls/time_picker + """ + + value: Optional[time] = field(default_factory=lambda: datetime.now().time()) + """ + The selected time that the picker should display. The default value is equal + to the current time. + """ + + modal: bool = False + """ + TBD + """ + + time_picker_entry_mode: Optional[TimePickerEntryMode] = None + """ + The initial mode of time entry method for the time picker dialog. + + Value is of type [`TimePickerEntryMode`](https://flet.dev/docs/reference/types/timepickerentrymode) + and defaults to `TimePickerEntryMode.DIAL`. + """ + + hour_label_text: Optional[str] = None + """ + The text that is displayed below the hour input text field. + + The default value is "Hour". + """ + + minute_label_text: Optional[str] = None + """ + The text that is displayed below the minute input text field. + + The default value is "Minute". + """ + + help_text: Optional[str] = None + """ + The text that is displayed at the top of the header. + + This is used to indicate to the user what they are selecting a time for. + The default value is "Enter time". + """ + + cancel_text: Optional[str] = None + """ + The text that is displayed on the cancel button. The default value is "Cancel". + """ + + confirm_text: Optional[str] = None + """ + The text that is displayed on the confirm button. The default value is "OK". + """ + + error_invalid_text: Optional[str] = None + """ + The error message displayed below the input text field if the input is not a + valid hour/minute. The default value is "Enter a valid time". + """ + + orientation: Optional[Orientation] = None + """ + The orientation of the dialog when displayed. Value is of type `Orientation` + enum which has the following possible values: `PORTRAIT` and `LANDSCAPE`. + """ + + barrier_color: OptionalColorValue = None + """ + TBD + """ + + on_change: OptionalControlEventHandler["TimePicker"] = None + """ + Fires when user clicks confirm button. `value` property is updated with selected + time. `e.data` also contains the selected time. + """ + + on_entry_mode_change: OptionalEventHandler[TimePickerEntryModeChangeEvent] = None + """ + Fires when the `time_picker_entry_mode` is changed. + + Event handler argument is of type + [`TimePickerEntryModeChangeEvent`](https://flet.dev/docs/reference/types/timepickerentrymodechangeevent). + """ diff --git a/sdk/python/packages/flet/src/flet/controls/material/tooltip.py b/sdk/python/packages/flet/src/flet/controls/material/tooltip.py new file mode 100644 index 000000000..4d03e5f9f --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/tooltip.py @@ -0,0 +1,171 @@ +from dataclasses import dataclass, field +from typing import Optional, Union + +from flet.controls.border_radius import ( + BorderRadius, +) +from flet.controls.box import BoxDecoration +from flet.controls.duration import Duration, DurationValue, OptionalDurationValue +from flet.controls.margin import OptionalMarginValue +from flet.controls.padding import OptionalPaddingValue +from flet.controls.text_style import TextStyle +from flet.controls.types import ( + MouseCursor, + OptionalColorValue, + OptionalNumber, + TextAlign, +) + + +class TooltipTriggerMode: + MANUAL = "manual" + """""" + + TAP = "tap" + """ + Tooltip will be shown after a single tap. + """ + + LONG_PRESS = "long_press" + """ + Tooltip will be shown after a long press. + """ + + +@dataclass +class Tooltip: + """ + Tooltips provide text labels which help explain the function of a button or + other user interface action. + """ + + message: str + """ + The text to display in the tooltip. + """ + + decoration: Optional[BoxDecoration] = field( + default_factory=lambda: BoxDecoration(border_radius=BorderRadius.all(4.0)) + ) + """ + The tooltip's background decoration. + """ + + enable_feedback: Optional[bool] = None + """ + When `True` (default) the tooltip should provide acoustic and/or haptic + feedback. + + For example, on Android a tap will produce a clicking sound and a long-press + will produce a short vibration, when feedback is enabled. + """ + + height: OptionalNumber = None + """ + The height of the tooltip's content. + """ + + vertical_offset: OptionalNumber = None + """ + The vertical gap between the control and the displayed tooltip. + """ + + margin: OptionalMarginValue = None + """ + The empty space that surrounds the tooltip. + + Value is of type [`Margin`](https://flet.dev/docs/reference/types/margin) + or a number. + """ + + padding: OptionalPaddingValue = None + """ + The amount of space by which to inset the tooltip's content. + + The value is an instance of + [`Padding`](https://flet.dev/docs/reference/types/padding) class or a number. + + On mobile, defaults to `16.0` logical pixels horizontally and `4.0` vertically. + On desktop, defaults to `8.0` logical pixels horizontally and `4.0` vertically. + """ + + bgcolor: OptionalColorValue = None + """ + Background [color](https://flet.dev/docs/reference/colors) of the tooltip. + """ + + text_style: Optional[TextStyle] = None + """ + The [TextStyle](https://flet.dev/docs/reference/types/textstyle) to use for the + message of the tooltip. + """ + + text_align: Optional[TextAlign] = None + """ + How the message of the tooltip is aligned horizontally. + + Value is of type [`TextAlign`](https://flet.dev/docs/reference/types/textalign) + and defaults to `TextAlign.LEFT`. + """ + + prefer_below: Optional[bool] = None + """ + Whether the tooltip defaults to being displayed below the control. + + If there is insufficient space to display the tooltip in the preferred + direction, the tooltip will be displayed in the opposite direction. + + Defaults to `True`. + """ + + show_duration: OptionalDurationValue = None + """ + The length of time, in milliseconds, that the tooltip will be shown after a + long press is released or a tap is released or mouse pointer exits the control. + """ + + wait_duration: DurationValue = field( + default_factory=lambda: Duration(milliseconds=800) + ) + """ + The length of time, in milliseconds, that a pointer must hover over a + tooltip's control before the tooltip will be shown. + + Defaults to 800 milliseconds. + """ + + exit_duration: OptionalDurationValue = None + """ + The length of time, in milliseconds, that the tooltip will be shown after a + long press is released or a tap is released or mouse pointer exits the control. + """ + + enable_tap_to_dismiss: bool = True + """ + Whether the tooltip can be dismissed by tapping on it. + + Defaults to `True`. + """ + + exclude_from_semantics: Optional[bool] = None + """ + Whether the tooltip's message should be excluded from the semantics tree. + + Defaults to `False`. + """ + + trigger_mode: Optional[TooltipTriggerMode] = None + """ + The mode of the tooltip's trigger. + + Value is of type + [`TooltipTriggerMode`](https://flet.dev/docs/reference/types/tooltiptriggermode). + """ + + mouse_cursor: Optional[MouseCursor] = None + """ + The cursor for a mouse pointer when it enters or is hovering over the content. + """ + + +TooltipValue = Union[str, Tooltip] diff --git a/sdk/python/packages/flet/src/flet/controls/material/vertical_divider.py b/sdk/python/packages/flet/src/flet/controls/material/vertical_divider.py new file mode 100644 index 000000000..2bb4d6f6a --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/material/vertical_divider.py @@ -0,0 +1,69 @@ +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.types import OptionalColorValue, OptionalNumber + +__all__ = ["VerticalDivider"] + + +@control("VerticalDivider") +class VerticalDivider(Control): + """ + A thin vertical line, with padding on either side. + + In the material design language, this represents a divider. + + Online docs: https://flet.dev/docs/controls/verticaldivider + """ + + width: OptionalNumber = None + """ + The divider's width. The divider itself is always drawn as a vertical line + that is centered within the width specified by this value. + + Defaults to `16.0`. + """ + + thickness: OptionalNumber = None + """ + The thickness of the line drawn within the divider. + + A divider with a thickness of `0.0` is always drawn as a line with a width of + exactly one device pixel. + + Defaults to `0.0`. + """ + + color: OptionalColorValue = None + """ + The [color](https://flet.dev/docs/reference/colors) to use when painting the + line. + """ + + leading_indent: OptionalNumber = None + """ + The amount of empty space to the leading edge of the divider. + + Defaults to `0.0`. + """ + + trailing_indent: OptionalNumber = None + """ + The amount of empty space to the trailing edge of the divider. + + Defaults to `0.0`. + """ + + def before_update(self): + super().before_update() + assert ( + self.width is None or self.width >= 0 + ), "width must be greater than or equal to 0" + assert ( + self.thickness is None or self.thickness >= 0 + ), "thickness must be greater than or equal to 0" + assert ( + self.leading_indent is None or self.leading_indent >= 0 + ), "leading_indent must be greater than or equal to 0" + assert ( + self.trailing_indent is None or self.trailing_indent >= 0 + ), "trailing_indent must be greater than or equal to 0" diff --git a/sdk/python/packages/flet/src/flet/controls/multi_view.py b/sdk/python/packages/flet/src/flet/controls/multi_view.py new file mode 100644 index 000000000..a7749e97c --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/multi_view.py @@ -0,0 +1,22 @@ +from typing import Any + +from flet.controls.base_control import control +from flet.controls.page_view import PageView + +__all__ = ["MultiView"] + + +@control() +class MultiView(PageView): + """ + TBD + """ + + view_id: int + """ + TBD + """ + initial_data: dict[str, Any] + """ + TBD + """ diff --git a/sdk/python/packages/flet/src/flet/controls/object_patch.py b/sdk/python/packages/flet/src/flet/controls/object_patch.py new file mode 100644 index 000000000..626cf9194 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/object_patch.py @@ -0,0 +1,933 @@ +# +# python-json-patch - An implementation of the JSON Patch format +# https://github.com/stefankoegl/python-json-patch +# +# Copyright (c) 2011 Stefan Kögl +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +import dataclasses +import weakref +from enum import Enum + +from flet.controls.keys import Key + +_ST_ADD = 0 +_ST_REMOVE = 1 + + +class Operation(Enum): + Replace = 0 + Add = 1 + Remove = 2 + Move = 3 + + +class ObjectPatchException(Exception): + """Base Object Patch exception""" + + +class InvalidObjectPatch(ObjectPatchException): + """Raised if an invalid Object Patch is created""" + + +class PatchOperation: + """A single operation inside a Object Patch.""" + + def __init__(self, operation): + if not operation.__contains__("path"): + raise InvalidObjectPatch("Operation must have a 'path' member") + + self.location = operation["path"] + self.operation = operation + + def __hash__(self): + return hash(frozenset(self.operation.items())) + + def __eq__(self, other): + if not isinstance(other, PatchOperation): + return False + return self.operation == other.operation + + def __ne__(self, other): + return not (self == other) + + @property + def path(self): + return self.location[:-1] + + @property + def key(self): + return self.location[-1] + + @key.setter + def key(self, value): + self.location[-1] = value + self.operation["path"] = self.location + + def walk(self, doc, part): + """Walks one step in doc and returns the referenced part""" + + if isinstance(doc, list): + try: + return doc[part] + + except IndexError: + raise ObjectPatchException(f"index '{part}' is out of bounds") from None + + # Else the object is a mapping or supports __getitem__ + # (so assume custom indexing) + try: + if hasattr(doc, "__getitem__"): + return doc[part] + else: + return getattr(doc, str(part)) + + except KeyError: + raise ObjectPatchException(f"member '{part}' not found in {doc}") from None + + def to_last(self, doc): + """Resolves ptr until the last step, returns (sub-doc, last-step)""" + + if not self.location: + return doc + + for part in self.location[:-1]: + doc = self.walk(doc, part) + + return doc + + +class RemoveOperation(PatchOperation): + """Removes an object property or an array element.""" + + def _on_undo_remove(self, path, key): + if self.path == path: + if self.key >= key: + self.key += 1 + else: + key -= 1 + return key + + def _on_undo_add(self, path, key): + if self.path == path: + if self.key > key: + self.key -= 1 + else: + key -= 1 + return key + + +class AddOperation(PatchOperation): + """Adds an object property or an array element.""" + + def _on_undo_remove(self, path, key): + if self.path == path: + if self.key > key: + self.key += 1 + else: + key += 1 + return key + + def _on_undo_add(self, path, key): + if self.path == path: + if self.key > key: + self.key -= 1 + else: + key += 1 + return key + + +class ReplaceOperation(PatchOperation): + """Replaces an object property or an array element by a new value.""" + + def _on_undo_remove(self, path, key): + return key + + def _on_undo_add(self, path, key): + return key + + +class MoveOperation(PatchOperation): + """Moves an object property or an array element to a new location.""" + + @property + def from_path(self): + return self.operation["from"][:-1] + + @property + def from_key(self): + return self.operation["from"][-1] + + @from_key.setter + def from_key(self, value): + self.operation["from"][-1] = value + + def _on_undo_remove(self, path, key): + if self.from_path == path: + if self.from_key >= key: + self.from_key += 1 + else: + key -= 1 + if self.path == path: + if self.key > key: + self.key += 1 + else: + key += 1 + return key + + def _on_undo_add(self, path, key): + if self.from_path == path: + if self.from_key > key: + self.from_key -= 1 + else: + key -= 1 + if self.path == path: + if self.key > key: + self.key -= 1 + else: + key += 1 + return key + + +class ObjectPatch: + """An Object Patch is a list of Patch Operations.""" + + def __init__(self, patch): + self.patch = patch + + def __str__(self): + return str(self.patch) + + @classmethod + def from_diff( + cls, + src, + dst, + control_cls, + ): + builder = DiffBuilder( + src, + dst, + control_cls=control_cls, + ) + builder._compare_values(None, [], None, src, dst, False) + ops = list(builder.execute()) + + return ( + cls(ops), + list(builder.get_added_controls()), + list(builder.get_removed_controls()), + ) + + def to_message(self): + state = {"i": 0} + paths = [state["i"]] + state["i"] += 1 + + def encode_path(path): + node = paths + parent = paths + parts = path + len_parts = len(parts) + if len_parts == 0: + return [0, 0] # root object + n = 0 + while n < len_parts - 1: + if len(parent) == 1: + parent.append({}) + node = parent[1].get(parts[n], None) + if node is None: + node = [state["i"]] + parent[1][parts[n]] = node + state["i"] += 1 + parent = node + n += 1 + return [node[0], parts[n]] + + ops = [] + for op in self.patch: + if op["op"] == "remove": + ops.append([Operation.Remove, *encode_path(op["path"])]) + elif op["op"] == "replace": + ops.append([Operation.Replace, *encode_path(op["path"]), op["value"]]) + elif op["op"] == "add": + ops.append([Operation.Add, *encode_path(op["path"]), op["value"]]) + elif op["op"] == "move": + ops.append( + [ + Operation.Move, + *encode_path(op["from"]), + *encode_path(op["path"]), + ] + ) + else: + raise ObjectPatchException(f"Unknown operation: {op['op']}") + + return [paths, *ops] + + +class DiffBuilder: + def __init__( + self, + src_doc, + dst_doc, + control_cls=None, + ): + self.control_cls = control_cls + self._added_dataclasses = {} + self._removed_dataclasses = {} + self.index_storage = [{}, {}] + self.index_storage2 = [[], []] + self.__root = root = [] + self.src_doc = src_doc + self.dst_doc = dst_doc + root[:] = [root, root, None] + + def get_added_controls(self): + for key, dc in self._added_dataclasses.items(): + configure_setattr_only = key in self._removed_dataclasses + yield from self._configure_dataclass( + dc, None, False, configure_setattr_only + ) + + def get_removed_controls(self): + for key, dc in self._removed_dataclasses.items(): + recurse = key not in self._added_dataclasses + yield from self._removed_controls(dc, recurse) + + def store_index(self, value, index, st): + typed_key = (value, type(value)) + try: + storage = self.index_storage[st] + stored = storage.get(typed_key) + if stored is None: + storage[typed_key] = [index] + else: + storage[typed_key].append(index) + + except TypeError: + self.index_storage2[st].append((typed_key, index)) + + def take_index(self, value, st): + typed_key = (value, type(value)) + try: + stored = self.index_storage[st].get(typed_key) + if stored: + return stored.pop() + + except TypeError: + storage = self.index_storage2[st] + for i in range(len(storage) - 1, -1, -1): + if storage[i][0] == typed_key: + return storage.pop(i)[1] + + def insert(self, op): + root = self.__root + last = root[0] + last[1] = root[0] = [last, root, op] + return root[0] + + def remove(self, index): + link_prev, link_next, _ = index + link_prev[1] = link_next + link_next[0] = link_prev + index[:] = [] + + def iter_from(self, start): + root = self.__root + curr = start[1] + while curr is not root: + yield curr[2] + curr = curr[1] + + def __iter__(self): + root = self.__root + curr = root[1] + while curr is not root: + yield curr[2] + curr = curr[1] + + def execute(self): + root = self.__root + curr = root[1] + while curr is not root: + if curr[1] is not root: + op_first, op_second = curr[2], curr[1][2] + if ( + op_first.location == op_second.location + and isinstance(op_first, RemoveOperation) + and isinstance(op_second, AddOperation) + ): + yield ReplaceOperation( + { + "op": "replace", + "path": op_second.location, + "value": op_second.operation["value"], + } + ).operation + curr = curr[1][1] + continue + yield curr[2].operation + curr = curr[1] + + def _item_added(self, parent, path, key, item, item_key=None, frozen=False): + # print("\n\n_item_added:", path, key, item, item_key) + index_key = item_key if item_key is not None else item + index = self.take_index(index_key, _ST_REMOVE) + if index is not None: + op = index[2] + # print("\n\n_ST_REMOVE:", op.__dict__, item) + + # compare moved item + src = op.operation["value"] + dst = item + + self._undo_dataclass_removed(src) + + if ( + dataclasses.is_dataclass(src) + and dataclasses.is_dataclass(dst) + and ((not frozen and src is dst) or (frozen and src is not dst)) + ): + self._compare_dataclasses( + src.parent, + _path_join(path, key), + src, + dst, + frozen, + ) + + if isinstance(op.key, int) and isinstance(key, int): + for v in self.iter_from(index): + op.key = v._on_undo_remove(op.path, op.key) + + self.remove(index) + if op.location != _path_join(path, key): + new_op = MoveOperation( + { + "op": "move", + "from": op.location, + "path": _path_join(path, key), + } + ) + self.insert(new_op) + else: + new_op = AddOperation( + { + "op": "add", + "path": _path_join(path, key), + "value": item, + } + ) + new_index = self.insert(new_op) + self.store_index(index_key, new_index, _ST_ADD) + self._dataclass_added(item, parent, frozen) + + def _item_removed(self, path, key, item, item_key=None, frozen=False): + # print("\n\n_item_removed:", path, key, item, item_key) + new_op = RemoveOperation( + {"op": "remove", "path": _path_join(path, key), "value": item} + ) + index_key = item_key if item_key is not None else item + index = self.take_index(index_key, _ST_ADD) + new_index = self.insert(new_op) + if index is not None: + op = index[2] + # print("\n\n_ST_ADD:", op.__dict__) + + # compare moved item + src = item + dst = op.operation["value"] + + self._undo_dataclass_added(dst) + + if ( + dataclasses.is_dataclass(src) + and dataclasses.is_dataclass(dst) + and ((not frozen and src is dst) or (frozen and src is not dst)) + ): + self._compare_dataclasses( + dst.parent, + _path_join(op.path, op.key), + src, + dst, + frozen, + ) + + # We can't rely on the op.key type since PatchOperation casts + # the .key property to int and this path wrongly ends up being taken + # for numeric string dict keys while the intention is to only handle lists. + # So we do an explicit check on the item affected by the op instead. + added_item = op.to_last(self.dst_doc) + if isinstance(added_item, list): + for v in self.iter_from(index): + op.key = v._on_undo_add(op.path, op.key) + + self.remove(index) + if new_op.location != op.location: + new_op = MoveOperation( + { + "op": "move", + "from": new_op.location, + "path": op.location, + } + ) + new_index[2] = new_op + + else: + self.remove(new_index) + + else: + self.store_index(index_key, new_index, _ST_REMOVE) + self._dataclass_removed(item) + + def _item_replaced(self, path, key, item): + # print("_item_replaced:", path, key, item, frozen) + self.insert( + ReplaceOperation( + { + "op": "replace", + "path": _path_join(path, key), + "value": item, + } + ) + ) + + def _compare_dicts(self, parent, path, src, dst, frozen): + # print("\n_compare_dicts:", path, src, dst) + + src_keys = set(src.keys()) + dst_keys = set(dst.keys()) + added_keys = dst_keys - src_keys + removed_keys = src_keys - dst_keys + + for key in removed_keys: + self._item_removed(path, str(key), src[key], frozen=frozen) + + for key in added_keys: + self._item_added(parent, path, str(key), dst[key], frozen=frozen) + + for key in src_keys & dst_keys: + self._compare_values(parent, path, key, src[key], dst[key], frozen) + + def _compare_lists(self, parent, path, src, dst, frozen): + # print("\n_compare_lists:", path, src, dst) + + len_src, len_dst = len(src), len(dst) + max_len = max(len_src, len_dst) + min_len = min(len_src, len_dst) + for key in range(max_len): + if key < min_len: + old, new = src[key], dst[key] + # print("\n\nCOMPARE LIST ITEM:", key, "\n\nOLD:", old, "\n\nNEW:", new) + + if isinstance(old, dict) and isinstance(new, dict): + self._compare_dicts(parent, _path_join(path, key), old, new, frozen) + + elif isinstance(old, list) and isinstance(new, list): + self._compare_lists(parent, _path_join(path, key), old, new, frozen) + + elif dataclasses.is_dataclass(old) and dataclasses.is_dataclass(new): + frozen = ( + (old is not None and hasattr(old, "_frozen")) + or (new is not None and hasattr(new, "_frozen")) + or frozen + ) + + old_control_key = get_control_key(old) + new_control_key = get_control_key(new) + + if (not frozen and old is new) or ( + frozen + and old is not new # not a cached control tree + and type(old) is type(new) # iteams are of the same type + and ( + old_control_key is None + or new_control_key is None + or old_control_key == new_control_key + ) # same list key or both None + ): + # print("\n\ncompare list dataclasses:", new) + self._compare_dataclasses( + parent, _path_join(path, key), old, new, frozen + ) + elif (not frozen and old is not new) or (frozen and old is not new): + # print( + # "\n\ndataclass removed and added:", + # "\n\nOLD:", + # old, + # "\n\nNEW:", + # new, + # ) + self._item_removed( + path, + key, + old, + item_key=(old_control_key, path) + if old_control_key is not None + else old, + frozen=frozen, + ) + self._item_added( + parent, + path, + key, + new, + item_key=(new_control_key, path) + if new_control_key is not None + else new, + frozen=frozen, + ) + + elif type(old) is not type(new) or old != new: + # print("removed and added:", old, new) + self._item_removed(path, key, old, frozen=frozen) + self._item_added(parent, path, key, new, frozen=frozen) + + elif len_src > len_dst: + control_key = get_control_key(src[key]) + self._item_removed( + path, + len_dst, + src[key], + item_key=(control_key, path) + if control_key is not None + else src[key], + frozen=frozen, + ) + + else: + control_key = get_control_key(dst[key]) + self._item_added( + parent, + path, + key, + dst[key], + item_key=(control_key, path) + if control_key is not None + else dst[key], + frozen=frozen, + ) + + def _compare_dataclasses(self, parent, path, src, dst, frozen): + # print("\n_compare_dataclasses:", path, src, dst, frozen) + + if ( + self.control_cls + and isinstance(parent, self.control_cls) + and parent.is_isolated() + and parent != self.dst_doc + ): + return # do not update isolated control's children + + if self.control_cls and isinstance(dst, self.control_cls): + if frozen and hasattr(src, "_i"): + dst._i = src._i + dst.init() + dst.before_update() + + if not frozen: + # in-place comparison + changes = getattr(dst, "__changes", {}) + prev_lists = getattr(dst, "__prev_lists", {}) + prev_dicts = getattr(dst, "__prev_dicts", {}) + prev_classes = getattr(dst, "__prev_classes", {}) + + # TODO - should optimize performance? + fields = {f.name: f for f in dataclasses.fields(dst)} + for field_name, change in changes.items(): + if field_name in fields: + old = change[0] + new = change[1] + + # print("_compare_values:changes", old, new) + + self._compare_values(dst, path, field_name, old, new, frozen) + + # update prev value + if isinstance(new, list): + new = new[:] + prev_lists[field_name] = new + elif isinstance(new, dict): + new = new.copy() + prev_dicts[field_name] = new + elif dataclasses.is_dataclass(new): + prev_classes[field_name] = new + + # compare lists + for field_name, old in list(prev_lists.items()): + if field_name in changes: + if new is None: + del prev_lists[field_name] + continue + new = getattr(dst, field_name) + self._compare_values(dst, path, field_name, old, new, frozen) + prev_lists[field_name] = new[:] + + # compare dicts + for field_name, old in list(prev_dicts.items()): + if field_name in changes: + if new is None: + del prev_dicts[field_name] + continue + new = getattr(dst, field_name) + self._compare_values(dst, path, field_name, old, new, frozen) + prev_dicts[field_name] = new.copy() + + # compare dataclasses + for field_name, old in list(prev_classes.items()): + if field_name in changes: + if new is None: + del prev_classes[field_name] + continue + new = getattr(dst, field_name) + self._compare_values(dst, path, field_name, old, new, frozen) + prev_classes[field_name] = new + + changes.clear() + else: + # frozen comparison + # print( + # "\nfrozen dataclass compare:", + # src, + # "\n\ndst:", + # dst, + # "\n\nparent:", + # parent, + # ) + for field in dataclasses.fields(dst): + if "skip" not in field.metadata: + old = getattr(src, field.name) + new = getattr(dst, field.name) + if field.name.startswith("on_"): + old = old is not None + new = new is not None + self._compare_values(dst, path, field.name, old, new, frozen) + self._dataclass_removed(src) + self._dataclass_added(dst, parent, frozen) + + def _compare_values(self, parent, path, key, src, dst, frozen): + # print("\n_compare_values:", path, key, src, dst, frozen) + + if isinstance(src, dict) and isinstance(dst, dict): + self._compare_dicts(parent, _path_join(path, key), src, dst, frozen) + + elif isinstance(src, list) and isinstance(dst, list): + if (len(src) == 0 and len(dst) > 0) or (len(src) > 0 and len(dst) == 0): + self._item_replaced(path, key, dst) + self._dataclass_removed(src) + self._dataclass_added(dst, parent, frozen) + else: + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + + elif dataclasses.is_dataclass(src) and dataclasses.is_dataclass(dst): + frozen = ( + (src is not None and hasattr(src, "_frozen")) + or (dst is not None and hasattr(dst, "_frozen")) + or frozen + ) + + # print("\n_compare_values:dataclasses", src, dst, frozen) + + if (not frozen and src is dst) or ( + frozen and src is not dst and type(src) is type(dst) + ): + self._compare_dataclasses( + parent, _path_join(path, key), src, dst, frozen + ) + elif (not frozen and src is not dst) or ( + frozen and type(src) is not type(dst) + ): + self._item_replaced(path, key, dst) + self._dataclass_removed(src) + self._dataclass_added(dst, parent, frozen) + + elif type(src) is not type(dst) or src != dst: + self._item_replaced(path, key, dst) + self._dataclass_removed(src) + self._dataclass_added(dst, parent, frozen) + + def _dataclass_added(self, item, parent, frozen): + if dataclasses.is_dataclass(item): + if parent: + if parent is item: + raise Exception(f"Parent is the same as item: {item}") + item._parent = weakref.ref(parent) + if frozen: + item._frozen = frozen + + # print("\n_dataclass_added:", self._get_dataclass_key(item)) + self._added_dataclasses[self._get_dataclass_key(item)] = item + + elif isinstance(item, dict): + for v in item.values(): + self._dataclass_added(v, parent, frozen) + + elif isinstance(item, list): + for v in item: + self._dataclass_added(v, parent, frozen) + + def _undo_dataclass_added(self, item): + # print("\n_undo_dataclass_added:", self._get_dataclass_key(item)) + self._added_dataclasses.pop(self._get_dataclass_key(item), None) + + def _dataclass_removed(self, item): + if dataclasses.is_dataclass(item): + # print("\n_dataclass_removed:", self._get_dataclass_key(item)) + self._removed_dataclasses[self._get_dataclass_key(item)] = item + + elif isinstance(item, dict): + for v in item.values(): + self._dataclass_removed(v) + + elif isinstance(item, list): + for v in item: + self._dataclass_removed(v) + + def _undo_dataclass_removed(self, item): + if dataclasses.is_dataclass(item): + # print("\n_undo_dataclass_removed:", self._get_dataclass_key(item)) + self._removed_dataclasses.pop(self._get_dataclass_key(item), None) + + def _get_dataclass_key(self, item): + return ( + item._i + if self.control_cls and isinstance(item, self.control_cls) + else str(id(item)) + ) + + def _configure_dataclass(self, item, parent, frozen, configure_setattr_only=False): + if dataclasses.is_dataclass(item): + # print("\n_configure_dataclass:", item, frozen, configure_setattr_only) + + if parent: + if parent is item: + raise Exception(f"Parent is the same as item: {item}") + item._parent = weakref.ref(parent) + + if hasattr(item, "_frozen"): + frozen = item._frozen + elif frozen: + item._frozen = frozen + + def control_setattr(obj, name, value): + if not name.startswith("_") and ( + name != "data" + or not self.control_cls + or not isinstance(obj, self.control_cls) + ): + if hasattr(obj, "_frozen"): + raise Exception( + "Controls inside data view cannot be updated." + ) from None + + if hasattr(obj, "__changes"): + old_value = getattr(obj, name, None) + if name.startswith("on_"): + old_value = old_value is not None + new_value = ( + value if not name.startswith("on_") else value is not None + ) + if old_value != new_value: + # print( + # f"\n\nset_attr: {obj.__class__.__name__}.{name} = " + # f"{new_value}, old: {old_value}" + # ) + changes = getattr(obj, "__changes") + changes[name] = (old_value, new_value) + object.__setattr__(obj, name, value) + + item.__class__.__setattr__ = control_setattr # type: ignore + + if self.control_cls and isinstance(item, self.control_cls): + if not configure_setattr_only: + item.init() + item.before_update() + yield item + + # recurse through fields + if not configure_setattr_only: + for field in dataclasses.fields(item): + if "skip" not in field.metadata: + yield from self._configure_dataclass( + getattr(item, field.name), item, frozen + ) + + if not frozen: + setattr(item, "__changes", {}) + + elif isinstance(item, dict): + for v in item.values(): + yield from self._configure_dataclass(v, parent, frozen) + + elif isinstance(item, list): + for v in item: + yield from self._configure_dataclass(v, parent, frozen) + + def _removed_controls(self, item, recurse): + if self.control_cls and isinstance(item, self.control_cls): + if hasattr(item, "__prev_lists"): + # recurse through list props + for item_list in getattr(item, "__prev_lists", {}).values(): + yield from self._removed_controls(item_list, recurse) + + # recurse through dict props + for item_dict in getattr(item, "__prev_dicts", {}).values(): + yield from self._removed_controls(item_dict, recurse) + + # recurse through dataclass props + for item_class in getattr(item, "__prev_classes", {}).values(): + yield from self._removed_controls(item_class, recurse) + elif recurse: + # recurse through fields + for field in dataclasses.fields(item): + if "skip" not in field.metadata: + yield from self._removed_controls( + getattr(item, field.name), + recurse, + ) + + yield item + + elif isinstance(item, dict): + for v in item.values(): + yield from self._removed_controls(v, recurse) + + elif isinstance(item, list): + for v in item: + yield from self._removed_controls(v, recurse) + + +def get_control_key(obj): + key = getattr(obj, "key", None) + return key.value if isinstance(key, Key) else key + + +def _path_join(path, key): + if key is None: + return path + return path + [key] diff --git a/sdk/python/packages/flet/src/flet/controls/padding.py b/sdk/python/packages/flet/src/flet/controls/padding.py new file mode 100644 index 000000000..fd90b1e53 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/padding.py @@ -0,0 +1,105 @@ +from dataclasses import dataclass +from typing import Optional, Union + +from flet.controls.types import Number +from flet.utils import deprecated + +__all__ = [ + "Padding", + "PaddingValue", + "OptionalPaddingValue", + "all", + "symmetric", + "only", +] + + +@dataclass +class Padding: + """ + Defines padding for all sides of a rectangle. + """ + + left: Number = 0 + """ + The padding value for the left side of the rectangle. + """ + + top: Number = 0 + """ + The padding value for the top side of the rectangle. + """ + + right: Number = 0 + """ + The padding value for the right side of the rectangle. + """ + + bottom: Number = 0 + """ + The padding value for the bottom side of the rectangle. + """ + + @classmethod + def all(cls, value: Number) -> "Padding": + """ + Applies the same padding to all sides. + """ + return Padding(left=value, top=value, right=value, bottom=value) + + @classmethod + def symmetric(cls, *, vertical: Number = 0, horizontal: Number = 0) -> "Padding": + """ + Applies `vertical` padding to top and bottom sides and `horizontal` padding to + left and right sides. + """ + return Padding(left=horizontal, top=vertical, right=horizontal, bottom=vertical) + + @classmethod + def only( + cls, *, left: Number = 0, top: Number = 0, right: Number = 0, bottom: Number = 0 + ) -> "Padding": + """ + Applies padding to the specified sides. + """ + return Padding(left=left, top=top, right=right, bottom=bottom) + + @classmethod + def zero(cls) -> "Padding": + return Padding.only() + + +@deprecated( + reason="Use Padding.all() instead", + version="0.70.0", + delete_version="0.73.0", + show_parentheses=True, +) +def all(value: float) -> Padding: + return Padding(left=value, top=value, right=value, bottom=value) + + +@deprecated( + reason="Use Padding.symmetric() instead", + version="0.70.0", + delete_version="0.73.0", + show_parentheses=True, +) +def symmetric(vertical: float = 0, horizontal: float = 0) -> Padding: + return Padding(left=horizontal, top=vertical, right=horizontal, bottom=vertical) + + +@deprecated( + reason="Use Padding.only() instead", + version="0.70.0", + delete_version="0.73.0", + show_parentheses=True, +) +def only( + left: float = 0, top: float = 0, right: float = 0, bottom: float = 0 +) -> Padding: + return Padding(left=left, top=top, right=right, bottom=bottom) + + +PaddingValue = Union[Number, Padding] +OptionalPaddingValue = Optional[PaddingValue] diff --git a/sdk/python/packages/flet/src/flet/controls/page.py b/sdk/python/packages/flet/src/flet/controls/page.py new file mode 100644 index 000000000..1cb7ceac6 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/page.py @@ -0,0 +1,804 @@ +import asyncio +import logging +import weakref +from collections.abc import Awaitable, Coroutine +from concurrent.futures import CancelledError, Future, ThreadPoolExecutor +from contextvars import ContextVar +from dataclasses import InitVar, dataclass, field +from functools import partial +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Optional, + TypeVar, +) +from urllib.parse import urlparse + +from flet.auth.authorization import Authorization +from flet.auth.oauth_provider import OAuthProvider +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.base_control import BaseControl, control +from flet.controls.control import Control +from flet.controls.control_event import ( + ControlEvent, + Event, + OptionalControlEventHandler, + OptionalEventHandler, +) +from flet.controls.core.view import View +from flet.controls.core.window import Window +from flet.controls.exceptions import FletException +from flet.controls.multi_view import MultiView +from flet.controls.page_view import PageView +from flet.controls.query_string import QueryString +from flet.controls.services.browser_context_menu import BrowserContextMenu +from flet.controls.services.clipboard import Clipboard +from flet.controls.services.service import Service +from flet.controls.services.shared_preferences import SharedPreferences +from flet.controls.services.storage_paths import StoragePaths +from flet.controls.services.url_launcher import UrlLauncher +from flet.controls.session_storage import SessionStorage +from flet.controls.types import ( + AppLifecycleState, + Brightness, + PagePlatform, + Wrapper, +) +from flet.utils import classproperty, is_pyodide + +if not is_pyodide(): + from flet.auth.authorization_service import AuthorizationService + + AuthorizationImpl = AuthorizationService +else: + AuthorizationImpl = Authorization + +if TYPE_CHECKING: + from flet.messaging.session import Session + from flet.pubsub.pubsub_client import PubSubClient + +try: + from typing import ParamSpec +except ImportError: + from typing_extensions import ParamSpec + + +logger = logging.getLogger("flet") +try: + from typing import ParamSpec +except ImportError: + from typing_extensions import ParamSpec + +_session_page = ContextVar("flet_session_page", default=None) + + +class context: + @classproperty + def page(cls) -> Optional["Page"]: + return _session_page.get() + + +AT = TypeVar("AT", bound=Authorization) +InputT = ParamSpec("InputT") +RetT = TypeVar("RetT") + + +@control("ServiceRegistry") +class ServiceRegistry(Service): + services: list[Service] = field(default_factory=list) + + +@dataclass +class RouteChangeEvent(Event["Page"]): + route: str + + +@dataclass +class ViewPopEvent(Event["Page"]): + route: str + view: Optional[View] = None + + +@dataclass +class KeyboardEvent(Event["Page"]): + key: str + shift: bool + ctrl: bool + alt: bool + meta: bool + + +@dataclass +class LoginEvent(Event["Page"]): + error: Optional[str] + error_description: Optional[str] + + +@dataclass +class InvokeMethodResults: + method_id: str + result: Optional[str] + error: Optional[str] + + +@dataclass +class AppLifecycleStateChangeEvent(Event["Page"]): + state: AppLifecycleState + + +@dataclass +class MultiViewAddEvent(Event["Page"]): + view_id: int + initial_data: Any + + +@dataclass +class MultiViewRemoveEvent(Event["Page"]): + view_id: int + + +class PageDisconnectedException(FletException): + def __init__(self, message): + super().__init__(message) + + +@control("Page", isolated=True, post_init_args=2) +class Page(PageView): + """ + Page is a container for `View` (https://flet.dev/docs/controls/view) controls. + + A page instance and the root view are automatically created when a new + user session started. + + Online docs: https://flet.dev/docs/controls/page + """ + + sess: InitVar["Session"] + """ + TBD + """ + + multi_views: list[MultiView] = field(default_factory=list) + """ + TBD + """ + + window: Window = field(default_factory=lambda: Window()) + """ + A class with properties/methods/events to control app's native OS window. + + Value is of type [Window](https://flet.dev/docs/reference/types/window). + """ + + browser_context_menu: BrowserContextMenu = field( + default_factory=lambda: BrowserContextMenu() + ) + """ + Used to enable or disable the context menu that appears when the user + right-clicks on the web page. + + Value is of type + [BrowserContextMenu](https://flet.dev/docs/reference/types/browsercontextmenu). + + 🌎 Web only. + """ + + shared_preferences: SharedPreferences = field( + default_factory=lambda: SharedPreferences() + ) + """ + TBD + """ + + clipboard: Clipboard = field(default_factory=lambda: Clipboard()) + """ + TBD + """ + + storage_paths: StoragePaths = field(default_factory=lambda: StoragePaths()) + """ + TBD + """ + + url_launcher: UrlLauncher = field(default_factory=lambda: UrlLauncher()) + """ + TBD + """ + + _user_services: ServiceRegistry = field(default_factory=lambda: ServiceRegistry()) + """ + TBD + """ + + _page_services: ServiceRegistry = field(default_factory=lambda: ServiceRegistry()) + """ + TBD + """ + + route: Optional[str] = None + """ + Get or sets page's navigation route. See + [Navigation and routing](https://flet.dev/docs/getting-started/navigation-and-routing) + section for more information and examples. + """ + + web: bool = False + """ + `True` if the application is running in the web browser. + """ + + pwa: bool = False + """ + `True` if the application is running as Progressive Web App (PWA). + + Value is read-only. + """ + + debug: bool = False + """ + `True` if Flutter client of Flet app is running in debug mode. + """ + + wasm: bool = False + """ + TBD + """ + + multi_view: bool = False + """ + TBD + """ + + platform: Optional[PagePlatform] = None + """ + Operating system the application is running on. + + Value is of type [PagePlatform](https://flet.dev/docs/reference/types/pageplatform). + """ + + platform_brightness: Optional[Brightness] = None + """ + The current brightness mode of the host platform. + + Value is read-only and of type + [Brightness](https://flet.dev/docs/reference/types/brightness). + """ + + client_ip: Optional[str] = None + """ + IP address of the connected user. + + 🌎 Web only. + """ + + client_user_agent: Optional[str] = None + """ + Browser details of the connected user. + + 🌎 Web only. + """ + + fonts: Optional[dict[str, str]] = None + """ + Defines the custom fonts to be used in the application. + + Value is a dictionary, in which the keys represent the font family name + used for reference and the values: + - Key: The font family name used for reference. + - Value: The font source, either an absolute URL or a relative path to a + local asset. The following font file formats are supported `.ttc`, `.ttf` + and `.otf`. + + Usage example [here](https://flet.dev/docs/cookbook/fonts#importing-fonts). + """ + + on_platform_brightness_change: OptionalControlEventHandler["Page"] = None + """ + Fires when brightness of app host platform has changed. + """ + + on_app_lifecycle_state_change: OptionalEventHandler[ + AppLifecycleStateChangeEvent + ] = None + """ + Triggers when app lifecycle state changes. + + Event handler argument is of type + [AppLifecycleStateChangeEvent](https://flet.dev/docs/reference/types/applifecyclestatechangeevent). + """ + + on_route_change: OptionalEventHandler[RouteChangeEvent] = None + """ + Fires when page route changes either programmatically, by editing + application URL or using browser Back/Forward buttons. + + Event handler argument is of type + [RouteChangeEvent](https://flet.dev/docs/reference/types/routechangeevent). + """ + + on_view_pop: OptionalEventHandler[ViewPopEvent] = None + """ + Fires when the user clicks automatic "Back" button in + [AppBar](https://flet.dev/docs/controls/appbar) control. + + Event handler argument is of type + [ViewPopEvent](https://flet.dev/docs/reference/types/viewpopevent). + """ + + on_keyboard_event: OptionalEventHandler[KeyboardEvent] = None + """ + Fires when a keyboard key is pressed. + + Event handler argument is of type + [KeyboardEvent](https://flet.dev/docs/reference/types/keyboardevent). + """ + + on_connect: OptionalControlEventHandler["Page"] = None + """ + Fires when a web user (re-)connects to a page session. + + It is not triggered when an app page is first opened, but is triggered when + the page is refreshed, or Flet web client has re-connected after computer + was unlocked. This event could be used to detect when a web user becomes + "online". + """ + + on_disconnect: OptionalControlEventHandler["Page"] = None + """ + Fires when a web user disconnects from a page session, i.e. closes browser + tab/window. + """ + + on_close: OptionalControlEventHandler["Page"] = None + """ + Fires when a session has expired after configured amount of time + (60 minutes by default). + """ + + on_login: OptionalEventHandler[LoginEvent] = None + """ + Fires upon successful or failed OAuth authorization flow. + + See [Authentication](https://flet.dev/docs/cookbook/authentication#checking-authentication-results) + guide for more information and examples. + """ + + on_logout: OptionalControlEventHandler["Page"] = None + """ + Fires after `page.logout()` call. + """ + + on_error: OptionalControlEventHandler["Page"] = None + """ + Fires when unhandled exception occurs. + """ + + on_multi_view_add: OptionalEventHandler[MultiViewAddEvent] = None + """ + TBD + """ + + on_multi_view_remove: OptionalEventHandler[MultiViewRemoveEvent] = None + """ + TBD + """ + + def __post_init__( + self, + ref, + sess: "Session", + ) -> None: + AdaptiveControl.__post_init__(self, ref) + self._i = 1 + self.__session = weakref.ref(sess) + + # page services + self._page_services.services = [ + self.browser_context_menu, + self.shared_preferences, + self.clipboard, + self.url_launcher, + self.storage_paths, + ] + self.__last_route = None + self.__query: QueryString = QueryString(self) + self.__session_storage: SessionStorage = SessionStorage() + self.__authorization: Optional[Authorization] = None + + def get_control(self, id: int) -> Optional[BaseControl]: + """ + Get a control by its `id`. + + Example: + + ```python + import flet as ft + + def main(page: ft.Page): + x = ft.IconButton(ft.Icons.ADD) + page.add(x) + print(type(page.get_control(x.uid))) + + ft.app(main) + ``` + """ + return self.get_session().index.get(id) + + def update(self, *controls) -> None: + if len(controls) == 0: + self.__update(self) + else: + self.__update(*controls) + + def get_session(self): + if sess := self.__session(): + return sess + raise Exception("An attempt to fetch destroyed session.") + + def __update(self, *controls: Control): + for c in controls: + self.get_session().patch_control(c) + + def error(self, message: str) -> None: + self.get_session().error(message) + + def before_event(self, e: ControlEvent): + if isinstance(e, RouteChangeEvent): + if self.__last_route == e.route: + return False + self.__last_route = e.route + self.query() + elif isinstance(e, ViewPopEvent): + view = next((v for v in self.views if v.route == e.data), None) + if view is None: + return False + e.view = view + return True + return super().before_event(e) + + def run_task( + self, + handler: Callable[InputT, Awaitable[RetT]], + *args: InputT.args, + **kwargs: InputT.kwargs, + ) -> Future[RetT]: + """ + Run `handler` coroutine as a new Task in the event loop associated with the + current page. + """ + _session_page.set(self) + assert asyncio.iscoroutinefunction(handler) + + future = asyncio.run_coroutine_threadsafe( + handler(*args, **kwargs), self.get_session().connection.loop + ) + + def _on_completion(f): + try: + exception = f.exception() + if exception: + raise exception + except CancelledError: + pass + + future.add_done_callback(_on_completion) + + return future + + def __context_wrapper(self, handler: Callable[..., Any]) -> Wrapper: + def wrapper(*args, **kwargs): + _session_page.set(self) + handler(*args, **kwargs) + + return wrapper + + def run_thread( + self, + handler: Callable[InputT, Any], + *args: InputT.args, + **kwargs: InputT.kwargs, + ) -> None: + """ + Run `handler` function as a new Thread in the executor associated with the + current page. + """ + handler_with_context = self.__context_wrapper(handler) + if is_pyodide(): + handler_with_context(*args, **kwargs) + else: + loop = self.get_session().connection.loop + loop.call_soon_threadsafe( + loop.run_in_executor, + self.executor, + partial(handler_with_context, *args, **kwargs), + ) + + def go( + self, route: str, skip_route_change_event: bool = False, **kwargs: Any + ) -> None: + """ + A helper method that updates [`page.route`](#route), calls + [`page.on_route_change`](#on_route_change) event handler to update views and + finally calls `page.update()`. + """ + self.route = route if not kwargs else route + self.query.post(kwargs) + + if not skip_route_change_event: + e = RouteChangeEvent( + name="route_change", control=self, data=self.route, route=self.route + ) + if self.on_route_change: + if asyncio.iscoroutinefunction(self.on_route_change): + self.run_task(self.on_route_change, e) + elif callable(self.on_route_change): + self.on_route_change(e) + + self.update() + self.query() # Update query url (required when using go) + + def get_upload_url(self, file_name: str, expires: int) -> str: + """ + Generates presigned upload URL for built-in upload storage: + + * `file_name` - a relative to upload storage path. + * `expires` - a URL time-to-live in seconds. + + For example: + + ```python + upload_url = page.get_upload_url("dir/filename.ext", 60) + ``` + + To enable built-in upload storage provide `upload_dir` argument to `flet.app()` + call: + + ```python + ft.app(main, upload_dir="uploads") + ``` + """ + return self.get_session().connection.get_upload_url(file_name, expires) + + async def login_async( + self, + provider: OAuthProvider, + fetch_user: bool = True, + fetch_groups: bool = False, + scope: Optional[list[str]] = None, + saved_token: Optional[str] = None, + on_open_authorization_url: Optional[ + Callable[[str], Coroutine[Any, Any, None]] + ] = None, + complete_page_html: Optional[str] = None, + redirect_to_page: Optional[bool] = False, + authorization: type[AT] = AuthorizationImpl, + ) -> AT: + """ + Starts OAuth flow. See [Authentication](/docs/cookbook/authentication) guide + for more information and examples. + """ + self.__authorization = authorization( + provider, + fetch_user=fetch_user, + fetch_groups=fetch_groups, + scope=scope, + ) + if saved_token is None: + authorization_url, state = self.__authorization.get_authorization_data() + auth_attrs = {"state": state} + if complete_page_html: + auth_attrs["completePageHtml"] = complete_page_html + if redirect_to_page: + up = urlparse(provider.redirect_url) + auth_attrs["completePageUrl"] = up._replace( + path=f"{self.get_session().connection.page_name}{self.route}" + ).geturl() + self.get_session().connection.oauth_authorize(auth_attrs) + if on_open_authorization_url: + await on_open_authorization_url(authorization_url) + else: + self.launch_url( + authorization_url, "flet_oauth_signin", web_popup_window=self.web + ) + else: + await self.__authorization.dehydrate_token_async(saved_token) + + e = LoginEvent(name="login", control=self, error="", error_description="") + if self.on_login: + if asyncio.iscoroutinefunction(self.on_login): + self.run_task(self.on_login, e) + elif callable(self.on_login): + self.on_login(e) + + return self.__authorization + + async def _authorize_callback_async(self, data: dict[str, str]) -> None: + assert self.__authorization + state = data.get("state") + assert state == self.__authorization.state + + if not self.web: + if self.platform in ["ios", "android"]: + # close web view on mobile + self.close_in_app_web_view() + else: + # activate desktop window + self.window.to_front() + e = LoginEvent( + error=data.get("error"), + error_description=data.get("error_description"), + control=self, + name="login", + ) + if not e.error: + # perform token request + + code = data.get("code") + assert code not in [None, ""] + try: + await self.__authorization.request_token_async(code) + except Exception as ex: + e.error = str(ex) + if self.on_login: + if asyncio.iscoroutinefunction(self.on_login): + self.run_task(self.on_login, e) + elif callable(self.on_login): + self.on_login(e) + + def logout(self) -> None: + """ + Clears current authentication context. See + [Authentication](/docs/cookbook/authentication#signing-out) guide for more + information and examples. + """ + self.__authorization = None + e = ControlEvent(name="logout", control=self) + if self.on_logout: + if asyncio.iscoroutinefunction(self.on_logout): + self.run_task(self.on_logout, e) + elif callable(self.on_logout): + self.on_logout(e) + + def launch_url( + self, + url: str, + web_window_name: Optional[str] = None, + web_popup_window: Optional[bool] = False, + window_width: Optional[int] = None, + window_height: Optional[int] = None, + ) -> None: + """ + Opens `url` in a new browser window. + + Optional method arguments: + + * `web_window_name` - window tab/name to open URL in: + [`UrlTarget.SELF`](https://flet.dev/docs/reference/types/urltarget#self) - the + same browser tab, [`UrlTarget.BLANK`](/docs/reference/types/urltarget#blank) - + a new browser tab (or in external + application on mobile device) or `` - a named tab. + * `web_popup_window` - set to `True` to display a URL in a browser popup + window. Defaults to `False`. + * `window_width` - optional, popup window width. + * `window_height` - optional, popup window height. + """ + self.url_launcher.launch_url( + url, + web_window_name=web_window_name, + web_popup_window=web_popup_window, + window_width=window_width, + window_height=window_height, + ) + + async def launch_url_async( + self, + url: str, + web_window_name: Optional[str] = None, + web_popup_window: Optional[bool] = False, + window_width: Optional[int] = None, + window_height: Optional[int] = None, + ) -> None: + """ + Opens `url` in a new browser window. + + Optional method arguments: + + * `web_window_name` - window tab/name to open URL in: [`UrlTarget.SELF`](https://flet.dev//docs/reference/types/urltarget#self) + - the same browser tab, [`UrlTarget.BLANK`](https://flet.dev//docs/reference/types/urltarget#blank) + - a new browser tab (or in external + application on mobile device) or `` - a named tab. + * `web_popup_window` - set to `True` to display a URL in a browser popup + window. Defaults to `False`. + * `window_width` - optional, popup window width. + * `window_height` - optional, popup window height. + """ + await self.url_launcher.launch_url_async( + url, + web_window_name=web_window_name, + web_popup_window=web_popup_window, + window_width=window_width, + window_height=window_height, + ) + + def can_launch_url_async(self, url: str): + """ + Checks whether the specified URL can be handled by some app installed on the + device. + + Returns `True` if it is possible to verify that there is a handler available. + A `False` return value can indicate either that there is no handler available, + or that the application does not have permission to check. For example: + + * On recent versions of Android and iOS, this will always return `False` unless + the application has been configuration to allow querying the system for launch + support. + * On web, this will always return `False` except for a few specific schemes + that are always assumed to be supported (such as http(s)), as web pages are + never allowed to query installed applications. + """ + return self.url_launcher.can_launch_url_async(url) + + def close_in_app_web_view(self) -> None: + """ + Closes in-app web view opened with `launch_url()`. + + 📱 Mobile only. + """ + self.url_launcher.close_in_app_web_view() + + async def close_in_app_web_view_async(self) -> None: + """ + Closes in-app web view opened with `launch_url()`. + + 📱 Mobile only. + """ + await self.url_launcher.close_in_app_web_view_async() + + # query + @property + def query(self) -> QueryString: + return self.__query + + # url + @property + def url(self) -> Optional[str]: + return self.get_session().connection.page_url + + # name + @property + def name(self) -> str: + return self.get_session().connection.page_name + + # loop + @property + def loop(self) -> asyncio.AbstractEventLoop: + return self.get_session().connection.loop + + # executor + @property + def executor(self) -> Optional[ThreadPoolExecutor]: + return self.get_session().connection.executor + + # auth + @property + def auth(self) -> Optional[Authorization]: + return self.__authorization + + # pubsub + @property + def pubsub(self) -> "PubSubClient": + return self.get_session().pubsub_client + + # session_storage + @property + def session(self) -> SessionStorage: + return self.__session_storage + + # services + @property + def services(self) -> list[Service]: + return self._user_services.services + + @services.setter + def services(self, value: list[Service]): + self._user_services.services = value diff --git a/sdk/python/packages/flet/src/flet/controls/page_view.py b/sdk/python/packages/flet/src/flet/controls/page_view.py new file mode 100644 index 000000000..fad932f23 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/page_view.py @@ -0,0 +1,446 @@ +import logging +from dataclasses import dataclass, field +from typing import ( + Optional, + Union, +) + +from flet.controls.adaptive_control import AdaptiveControl +from flet.controls.animation import AnimationCurve +from flet.controls.base_control import BaseControl, control +from flet.controls.box import BoxDecoration +from flet.controls.control import Control +from flet.controls.control_event import ( + Event, + OptionalControlEventHandler, + OptionalEventHandler, +) +from flet.controls.core.view import View +from flet.controls.cupertino.cupertino_app_bar import CupertinoAppBar +from flet.controls.cupertino.cupertino_navigation_bar import CupertinoNavigationBar +from flet.controls.dialog_control import DialogControl +from flet.controls.duration import OptionalDurationValue +from flet.controls.keys import ScrollKey +from flet.controls.material.app_bar import AppBar +from flet.controls.material.bottom_app_bar import BottomAppBar +from flet.controls.material.floating_action_button import FloatingActionButton +from flet.controls.material.navigation_bar import NavigationBar +from flet.controls.material.navigation_drawer import NavigationDrawer +from flet.controls.padding import OptionalPaddingValue, Padding +from flet.controls.scrollable_control import OnScrollEvent +from flet.controls.theme import Theme +from flet.controls.transform import OffsetValue +from flet.controls.types import ( + CrossAxisAlignment, + FloatingActionButtonLocation, + LocaleConfiguration, + MainAxisAlignment, + OptionalColorValue, + OptionalNumber, + ScrollMode, + ThemeMode, +) +from flet.utils import deprecated + +logger = logging.getLogger("flet") + + +@dataclass +class PageMediaData: + padding: Padding + view_padding: Padding + view_insets: Padding + + +@dataclass +class PageResizeEvent(Event["PageView"]): + width: float + height: float + + +@control("PageView", isolated=True, kw_only=True) +class PageView(AdaptiveControl): + """ + TBD + """ + + views: list[View] = field(default_factory=lambda: [View()]) + _overlay: "Overlay" = field(default_factory=lambda: Overlay()) + _dialogs: "Dialogs" = field(default_factory=lambda: Dialogs()) + + theme_mode: Optional[ThemeMode] = ThemeMode.SYSTEM + """ + The page's theme mode. + + Value is of type [`ThemeMode`](https://flet.dev/docs/reference/types/thememode) and + defaults to `ThemeMode.SYSTEM`. + """ + theme: Optional[Theme] = None + """ + Customizes the theme of the application when in light theme mode. Currently, a + theme can only be automatically generated from a "seed" color. For example, to + generate light theme from a green color. + + Value is an instance of the `Theme()` class - more information in the [theming](https://flet.dev/docs/cookbook/theming) + guide. + """ + dark_theme: Optional[Theme] = None + """ + Customizes the theme of the application when in dark theme mode. + + Value is an instance of the `Theme()` class - more information in the + [theming](https://flet.dev/docs/cookbook/theming) guide. + """ + locale_configuration: Optional[LocaleConfiguration] = None + show_semantics_debugger: Optional[bool] = None + """ + `True` turns on an overlay that shows the accessibility information reported by the + framework. + """ + width: OptionalNumber = None + height: OptionalNumber = None + title: Optional[str] = None + media: Optional[PageMediaData] = None + scroll_event_interval: OptionalNumber = None + on_resized: OptionalEventHandler["PageResizeEvent"] = None + """ + Fires when a user resizes a browser or native OS window containing Flet app, for + example: + + ```python + def page_resized(e): + print("New page size:", page.window.width, page.window_height) + + page.on_resized = page_resized + ``` + + Event handler argument is of type [`WindowResizeEvent`](https://flet.dev/docs/reference/types/windowresizeevent). + """ + on_media_change: OptionalControlEventHandler["PageView"] = None + """ + Fires when `page.media` has changed. + + Event handler argument is of type + [`PageMediaData`](https://flet.dev/docs/docs/reference/types/pagemediadata). + """ + on_scroll: OptionalEventHandler["OnScrollEvent"] = None + """ + Fires when page's scroll position is changed by a user. + + Event handler argument is of type + [`OnScrollEvent`](https://flet.dev/docs/reference/types/onscrollevent). + """ + + def __default_view(self) -> View: + assert len(self.views) > 0, "views list is empty." + return self.views[0] + + def update(self, *controls) -> None: + if len(controls) == 0: + self.page.update(self) + else: + self.page.update(*controls) + + def add(self, *controls: Control) -> None: + """ + Adds controls to the page. + + ```python + page.add(ft.Text("Hello!"), ft.FilledButton("Button")) + ``` + """ + self.controls.extend(controls) + self.update() + + def insert(self, at: int, *controls: Control) -> None: + """ + Inserts controls at specific index of `page.controls` list. + """ + n = at + for c in controls: + self.controls.insert(n, c) + n += 1 + self.update() + + def remove(self, *controls: Control) -> None: + """ + Removes specific controls from `page.controls` list. + """ + for c in controls: + self.controls.remove(c) + self.update() + + def remove_at(self, index: int) -> None: + """ + Remove controls from `page.controls` list at specific index. + """ + self.controls.pop(index) + self.update() + + def clean(self) -> None: + self.controls.clear() + self.update() + + def scroll_to( + self, + offset: Optional[float] = None, + delta: Optional[float] = None, + scroll_key: Union[ScrollKey, str, int, float, bool, None] = None, + duration: OptionalDurationValue = None, + curve: Optional[AnimationCurve] = None, + ) -> None: + """ + Moves scroll position to either absolute `offset`, relative `delta` or jump to + the control with specified `scroll_key`. + + See [`Column.scroll_to()`](https://flet.dev/docs/controls/column#scroll_tooffset-delta-key-duration-curve) + for method details and examples. + """ + self.__default_view().scroll_to( + offset=offset, + delta=delta, + scroll_key=scroll_key, + duration=duration, + curve=curve, + ) + + @deprecated( + reason="Use Page.show_dialog() instead", + version="0.70.0", + delete_version="0.73.0", + show_parentheses=True, + ) + def open(self, control: DialogControl) -> None: + self.show_dialog(control) + + def show_dialog(self, dialog: DialogControl) -> None: + """ + TBD + """ + if dialog in self._dialogs.controls: + raise Exception("Dialog is already opened") + + original_on_dismiss = dialog.on_dismiss + + def wrapped_on_dismiss(*args, **kwargs): + if dialog in self._dialogs.controls: + self._dialogs.controls.remove(dialog) + self._dialogs.update() + if ( + original_on_dismiss + and not hasattr(dialog, "_force_close") + and args[ + 0 + ].data # e.data == False for TimePicker and DatePicker if they were + # dismissed without changing the value + ): + original_on_dismiss(*args, **kwargs) + dialog.on_dismiss = original_on_dismiss + if hasattr(dialog, "_force_close"): + del dialog._force_close + + dialog.open = True + dialog.on_dismiss = wrapped_on_dismiss + + self._dialogs.controls.append(dialog) + self._dialogs.update() + + @deprecated( + reason="Use Page.pop_dialog() instead", + version="0.70.0", + delete_version="0.73.0", + show_parentheses=True, + ) + def close(self, control: DialogControl) -> None: + self.pop_dialog() + + def pop_dialog(self) -> Optional[DialogControl]: + # get the top most opened dialog + dialog = next( + (dlg for dlg in reversed(self._dialogs.controls) if dlg.open), None + ) + if not dialog: + return None + dialog.open = False + dialog._force_close = True + dialog.update() + return dialog + + # overlay + @property + def overlay(self) -> list[BaseControl]: + return self._overlay.controls + + # controls + @property + def controls(self) -> list[BaseControl]: + return self.__default_view().controls + + @controls.setter + def controls(self, value: list[BaseControl]): + self.__default_view().controls = value + + # appbar + @property + def appbar(self) -> Union[AppBar, CupertinoAppBar, None]: + return self.__default_view().appbar + + @appbar.setter + def appbar(self, value: Union[AppBar, CupertinoAppBar, None]): + self.__default_view().appbar = value + + # bottom_appbar + @property + def bottom_appbar(self) -> Optional[BottomAppBar]: + return self.__default_view().bottom_appbar + + @bottom_appbar.setter + def bottom_appbar(self, value: Optional[BottomAppBar]): + self.__default_view().bottom_appbar = value + + # navigation_bar + @property + def navigation_bar(self) -> Optional[Union[NavigationBar, CupertinoNavigationBar]]: + return self.__default_view().navigation_bar + + @navigation_bar.setter + def navigation_bar( + self, + value: Optional[Union[NavigationBar, CupertinoNavigationBar]], + ): + self.__default_view().navigation_bar = value + + # drawer + @property + def drawer(self) -> Optional[NavigationDrawer]: + return self.__default_view().drawer + + @drawer.setter + def drawer(self, value: Optional[NavigationDrawer]): + self.__default_view().drawer = value + + # end_drawer + @property + def end_drawer(self) -> Optional[NavigationDrawer]: + return self.__default_view().end_drawer + + @end_drawer.setter + def end_drawer(self, value: Optional[NavigationDrawer]): + self.__default_view().end_drawer = value + + # decoration + @property + def decoration(self) -> Optional[BoxDecoration]: + return self.__default_view().decoration + + @decoration.setter + def decoration(self, value: Optional[BoxDecoration]): + self.__default_view().decoration = value + + # foreground_decoration + @property + def foreground_decoration(self) -> Optional[BoxDecoration]: + return self.__default_view().foreground_decoration + + @foreground_decoration.setter + def foreground_decoration(self, value: Optional[BoxDecoration]): + self.__default_view().foreground_decoration = value + + # floating_action_button + @property + def floating_action_button(self) -> Optional[FloatingActionButton]: + return self.__default_view().floating_action_button + + @floating_action_button.setter + def floating_action_button(self, value: Optional[FloatingActionButton]): + self.__default_view().floating_action_button = value + + # floating_action_button_location + @property + def floating_action_button_location( + self, + ) -> Optional[Union[FloatingActionButtonLocation, OffsetValue]]: + return self.__default_view().floating_action_button_location + + @floating_action_button_location.setter + def floating_action_button_location( + self, value: Optional[Union[FloatingActionButtonLocation, OffsetValue]] + ): + self.__default_view().floating_action_button_location = value + + # horizontal_alignment + @property + def horizontal_alignment(self) -> Optional[CrossAxisAlignment]: + return self.__default_view().horizontal_alignment + + @horizontal_alignment.setter + def horizontal_alignment(self, value: Optional[CrossAxisAlignment]): + self.__default_view().horizontal_alignment = value + + # vertical_alignment + @property + def vertical_alignment(self) -> Optional[MainAxisAlignment]: + return self.__default_view().vertical_alignment + + @vertical_alignment.setter + def vertical_alignment(self, value: Optional[MainAxisAlignment]): + self.__default_view().vertical_alignment = value + + # spacing + @property + def spacing(self) -> OptionalNumber: + return self.__default_view().spacing + + @spacing.setter + def spacing(self, value: OptionalNumber): + self.__default_view().spacing = value + + # padding + @property + def padding(self) -> OptionalPaddingValue: + return self.__default_view().padding + + @padding.setter + def padding(self, value: OptionalPaddingValue): + self.__default_view().padding = value + + # bgcolor + @property + def bgcolor(self) -> OptionalColorValue: + return self.__default_view().bgcolor + + @bgcolor.setter + def bgcolor(self, value: OptionalColorValue): + self.__default_view().bgcolor = value + + # scroll + @property + def scroll(self) -> Optional[ScrollMode]: + return self.__default_view().scroll + + @scroll.setter + def scroll(self, value: Optional[ScrollMode]): + self.__default_view().scroll = value + + # auto_scroll + @property + def auto_scroll(self): + return self.__default_view().auto_scroll + + @auto_scroll.setter + def auto_scroll(self, value: bool): + self.__default_view().auto_scroll = value + + # Magic methods + def __contains__(self, item: Control) -> bool: + return item in self.controls + + +@control("Overlay") +class Overlay(BaseControl): + controls: list[BaseControl] = field(default_factory=list) + + +@control("Dialogs") +class Dialogs(BaseControl): + controls: list[DialogControl] = field(default_factory=list) diff --git a/sdk/python/packages/flet/src/flet/controls/painting.py b/sdk/python/packages/flet/src/flet/controls/painting.py new file mode 100644 index 000000000..0f43cf556 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/painting.py @@ -0,0 +1,281 @@ +import math +from dataclasses import dataclass, field +from enum import Enum +from typing import Optional + +from flet.controls.blur import BlurValue +from flet.controls.gradients import GradientTileMode +from flet.controls.transform import OffsetValue +from flet.controls.types import ( + BlendMode, + Number, + OptionalColorValue, + OptionalNumber, + StrokeCap, + StrokeJoin, +) + +__all__ = [ + "Paint", + "PaintGradient", + "PaintLinearGradient", + "PaintRadialGradient", + "PaintSweepGradient", + "PaintingStyle", +] + + +class PaintingStyle(Enum): + FILL = "fill" + STROKE = "stroke" + + +@dataclass(kw_only=True) +class PaintGradient: + _type: Optional[str] = field(init=False, repr=False, compare=False, default=None) + + +@dataclass +class PaintLinearGradient(PaintGradient): + """ + More information on Linear gradient + https://api.flutter.dev/flutter/dart-ui/Gradient/Gradient.linear.html + """ + + begin: Optional[OffsetValue] + """ + An instance of https://flet.dev/docs/reference/types/offset. The offset at which + stop 0.0 of the gradient is placed. + """ + + end: Optional[OffsetValue] + """ + An instance of https://flet.dev/docs/reference/types/offset. The offset at which + stop 1.0 of the gradient is placed. + """ + + colors: list[str] + """ + The https://flet.dev/docs/reference/colors the gradient should obtain at each of + the stops. This list must contain at least two colors. + + If `stops` is provided, this list must have the same length as `stops`. + """ + + color_stops: Optional[list[Number]] = None + """ + A list of values from `0.0` to `1.0` that denote fractions along the gradient. + + If provided, this list must have the same length as `colors`. If the first value + is not `0.0`, then a stop with position `0.0` and a color equal to the first color + in `colors` is implied. If the last value is not `1.0`, then a stop with position + `1.0` and a color equal to the last color in `colors` is implied. + """ + + tile_mode: GradientTileMode = GradientTileMode.CLAMP + """ + How this gradient should tile the plane beyond in the region before `begin` and + after `end`. The value is `GradientTileMode` enum with supported values: `CLAMP` + (default), `DECAL`, `MIRROR`, `REPEATED`. More info here: + https://api.flutter.dev/flutter/dart-ui/TileMode.html + """ + + def __post_init__(self): + self._type = "linear" + + +@dataclass +class PaintRadialGradient(PaintGradient): + """ + More information on Radial gradient + https://api.flutter.dev/flutter/dart-ui/Gradient/Gradient.radial.html + """ + + center: Optional[OffsetValue] + """ + An instance of https://flet.dev/docs/reference/types/offset class. The center of + the gradient. + """ + + radius: Number + """ + The radius of the gradient. + """ + + colors: list[str] + """ + The https://flet.dev/docs/reference/colors the gradient should obtain at each of + the stops. This list must contain at least two colors. + + If `stops` is provided, this list must have the same length as `stops`. + """ + + color_stops: Optional[list[float]] = None + """ + A list of values from `0.0` to `1.0` that denote fractions along the gradient. + + If provided, this list must have the same length as `colors`. If the first value + is not `0.0`, then a stop with position `0.0` and a color equal to the first color + in `colors` is implied. If the last value is not `1.0`, then a stop with position + `1.0` and a color equal to the last color in `colors` is implied. + """ + + tile_mode: GradientTileMode = GradientTileMode.CLAMP + """ + How this gradient should tile the plane beyond in the region before `begin` and + after `end`. The value is of type + https://flet.dev/docs/reference/types/gradienttilemode. + """ + + focal: Optional[OffsetValue] = None + """ + The focal point of the gradient. If specified, the gradient will appear to be + focused along the vector from `center` to focal. + """ + + focal_radius: Number = 0.0 + """ + The radius of the focal point of gradient, as a fraction of the shortest side of + the paint box. + + For example, if a radial gradient is painted on a box that is `100.0` pixels wide + and `200.0` pixels tall, then a radius of `1.0` will place the `1.0` stop at + `100.0` pixels from the focal point. + """ + + def __post_init__(self): + self._type = "radial" + + +@dataclass +class PaintSweepGradient(PaintGradient): + """ + More information on Sweep gradient + https://api.flutter.dev/flutter/dart-ui/Gradient/Gradient.sweep.html + """ + + center: Optional[OffsetValue] + """ + An instance of https://flet.dev/docs/reference/types/offset class. The center of + the gradient. + """ + + colors: list[str] + """ + The https://flet.dev/docs/reference/colors the gradient should obtain at each of + the stops. This list must contain at least two colors. + + If `stops` is provided, this list must have the same length as `stops`. + """ + + color_stops: Optional[list[Number]] = None + """ + A list of values from `0.0` to `1.0` that denote fractions along the gradient. + + If provided, this list must have the same length as `colors`. If the first value + is not `0.0`, then a stop with position `0.0` and a color equal to the first color + in `colors` is implied. If the last value is not `1.0`, then a stop with position + `1.0` and a color equal to the last color in `colors` is implied. + """ + + tile_mode: GradientTileMode = GradientTileMode.CLAMP + """ + How this gradient should tile the plane beyond in the region before `begin` and + after `end`. The value is of type + https://flet.dev/docs/reference/types/gradienttilemode. + """ + + start_angle: Number = 0.0 + """ + The angle in https://en.wikipedia.org/wiki/Radian at which stop 0.0 of the + gradient is placed. Defaults to 0.0. + """ + + end_angle: Number = math.pi * 2 + """ + The angle in radians at which stop 1.0 of the gradient is placed. Defaults to + math.pi * 2. + """ + + rotation: OptionalNumber = None + """ + The rotation of the gradient in https://en.wikipedia.org/wiki/Radian, around the + center-point of its bounding box. + """ + + def __post_init__(self): + self._type = "sweep" + + +@dataclass +class Paint: + """ + A description of the style to use when drawing a shape on the canvas. + """ + + color: OptionalColorValue = None + """ + The https://flet.dev/docs/reference/colors to use when stroking or filling a shape. + Defaults to opaque black. + """ + + blend_mode: Optional[BlendMode] = None + """ + A blend mode to apply when a shape is drawn or a layer is composited. + + Value is of type https://flet.dev/docs/reference/types/blendmode and defaults to + `BlendMode.SRC_OVER`. + """ + + blur_image: Optional[BlurValue] = None + """ + Blur image when drawing it on a canvas. + + See https://flet.dev/docs/controls/container#blur for more information. + """ + + anti_alias: Optional[bool] = None + """ + Whether to apply anti-aliasing to lines and images drawn on the canvas. + + Defaults to `True`. + """ + + gradient: Optional[PaintGradient] = None + """ + Configures gradient paint. Value is an instance of one of the following classes: + + * https://flet.dev/docs/reference/types/paintlineargradient + * https://flet.dev/docs/reference/types/paintradialgradient + * https://flet.dev/docs/reference/types/paintsweepgradient + """ + + stroke_cap: Optional[StrokeCap] = None + """ + TBD + """ + + stroke_join: Optional[StrokeJoin] = None + """ + TBD + """ + + stroke_miter_limit: OptionalNumber = None + """ + TBD + """ + + stroke_width: OptionalNumber = None + """ + TBD + """ + + stroke_dash_pattern: Optional[list[Number]] = None + """ + TBD + """ + + style: Optional[PaintingStyle] = None + """ + TBD + """ diff --git a/sdk/python/packages/flet/src/flet/core/querystring.py b/sdk/python/packages/flet/src/flet/controls/query_string.py similarity index 82% rename from sdk/python/packages/flet/src/flet/core/querystring.py rename to sdk/python/packages/flet/src/flet/controls/query_string.py index 5fb91fa4d..bdc1bdb32 100644 --- a/sdk/python/packages/flet/src/flet/core/querystring.py +++ b/sdk/python/packages/flet/src/flet/controls/query_string.py @@ -1,5 +1,8 @@ import re import urllib.parse +import weakref + +__all__ = ["QueryString", "UrlComponents"] class UrlComponents: @@ -64,8 +67,8 @@ class QueryString(UrlComponents): """ - def __init__(self, page=None): - self.page = page + def __init__(self, page): + self.__page = weakref.ref(page) self.url = None def get(self, key: str) -> str: @@ -90,13 +93,16 @@ def __call__(self): """ Call dunder method updates url after updating `Page` """ - self.url = self.page.url + self.page.route - - # Checking if self.url is encoded and decoding it accordingly - if self._is_encoded() is True: - self.url = ( - self.page.url - + urllib.parse.urlparse(self.url).path - + "?" - + self._decode_url_component(self._querystring_part(url_string=True)) - ) + if page := self.__page(): + self.url = page.url + page.route + + # Checking if self.url is encoded and decoding it accordingly + if self._is_encoded() is True: + self.url = ( + page.url + + urllib.parse.urlparse(self.url).path + + "?" + + self._decode_url_component( + self._querystring_part(url_string=True) + ) + ) diff --git a/sdk/python/packages/flet/src/flet/controls/ref.py b/sdk/python/packages/flet/src/flet/controls/ref.py new file mode 100644 index 000000000..0774674ff --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/ref.py @@ -0,0 +1,20 @@ +import weakref +from typing import Generic, Optional, TypeVar + +T = TypeVar("T") +__all__ = ["Ref"] + + +class Ref(Generic[T]): + """Utility class which allows defining a reference to a control.""" + + def __init__(self, value: Optional[T] = None): + self.current = value + + @property + def current(self) -> Optional[T]: + return self._ref() if self._ref else None + + @current.setter + def current(self, value: Optional[T]): + self._ref = weakref.ref(value) if value is not None else None diff --git a/sdk/python/packages/flet/src/flet/controls/scrollable_control.py b/sdk/python/packages/flet/src/flet/controls/scrollable_control.py new file mode 100644 index 000000000..b9c2adfc1 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/scrollable_control.py @@ -0,0 +1,236 @@ +import asyncio +from dataclasses import dataclass +from enum import Enum +from typing import Optional, Union + +from flet.controls.animation import AnimationCurve +from flet.controls.base_control import control +from flet.controls.control import Control +from flet.controls.control_event import Event, OptionalEventHandler +from flet.controls.duration import OptionalDurationValue +from flet.controls.keys import ScrollKey +from flet.controls.types import ( + Number, + OptionalNumber, + ScrollMode, +) + +__all__ = ["ScrollableControl", "OnScrollEvent", "ScrollType", "ScrollDirection"] + + +class ScrollType(Enum): + START = "start" + UPDATE = "update" + END = "end" + USER = "user" + OVERSCROLL = "overscroll" + + +class ScrollDirection(Enum): + IDLE = "idle" + FORWARD = "forward" + REVERSE = "reverse" + + +@dataclass +class OnScrollEvent(Event["ScrollableControl"]): + event_type: ScrollType + pixels: float + min_scroll_extent: float + max_scroll_extent: float + viewport_dimension: float + scroll_delta: Optional[float] = None + direction: Optional[ScrollDirection] = None + overscroll: Optional[float] = None + velocity: Optional[float] = None + + +@control(kw_only=True) +class ScrollableControl(Control): + scroll: Optional[ScrollMode] = None + """ + Enables a vertical scrolling for the Column to prevent its content overflow. + + Value is of type [`ScrollMode`](https://flet.dev/docs/reference/types/scrollmode) + and defaults to `ScrollMode.None`. + """ + auto_scroll: bool = False + """ + `True` if scrollbar should automatically move its position to the end when children + updated. Must be `False` for `scroll_to()` method to work. + """ + scroll_interval: Number = 10 + """ + Throttling in milliseconds for `on_scroll` event. + + Defaults to `10`. + """ + on_scroll: OptionalEventHandler[OnScrollEvent] = None + """ + Fires when scroll position is changed by a user. + + Event handler argument is an instance of [`OnScrollEvent`](https://flet.dev/docs/reference/types/onscrollevent) + class. + """ + + def scroll_to( + self, + offset: OptionalNumber = None, + delta: OptionalNumber = None, + scroll_key: Union[ScrollKey, str, int, float, bool, None] = None, + duration: OptionalDurationValue = None, + curve: Optional[AnimationCurve] = None, + ): + """ + Moves scroll position to either absolute `offset`, relative `delta` or jump to + the control with specified `key`. + + `offset` is an absolute value between minimum and maximum extents of a + scrollable control, for example: + + ```python + products.scroll_to(offset=100, duration=1000) + ``` + + `offset` could be a negative to scroll from the end of a scrollable. For + example, to scroll to the very end: + + ```python + products.scroll_to(offset=-1, duration=1000) + ``` + + `delta` allows moving scroll relatively to the current position. Use positive + `delta` to scroll forward and negative `delta` to scroll backward. For example, + to move scroll on 50 pixels forward: + + ```python + products.scroll_to(delta=50) + ``` + + `key` allows moving scroll position to a control with specified `key`. Most of + Flet controls have `key` property which is translated to Flutter as + "global key". `key` must be unique for the entire page/view. For example: + + ```python + import flet as ft + + def main(page: ft.Page): + cl = ft.Column( + spacing=10, + height=200, + width=200, + scroll=ft.ScrollMode.ALWAYS, + ) + for i in range(0, 50): + cl.controls.append(ft.Text(f"Text line {i}", key=str(i))) + + def scroll_to_key(e): + cl.scroll_to(key="20", duration=1000) + + page.add( + ft.Container(cl, border=ft.border.all(1)), + ft.ElevatedButton("Scroll to key '20'", on_click=scroll_to_key), + ) + + ft.app(main) + ``` + + Note: + `scroll_to()` method won't work with `ListView` and `GridView` controls + building their items dynamically. + + `duration` is scrolling animation duration in milliseconds. Defaults to `0` - + no animation. + + `curve` configures animation curve. Property value is [`AnimationCurve`](https://flet.dev/docs/reference/types/animationcurve) + enum. + Defaults to `AnimationCurve.EASE`. + """ + asyncio.create_task( + self.scroll_to_async(offset, delta, scroll_key, duration, curve) + ) + + async def scroll_to_async( + self, + offset: Optional[float] = None, + delta: Optional[float] = None, + scroll_key: Union[ScrollKey, str, int, float, bool, None] = None, + duration: OptionalDurationValue = None, + curve: Optional[AnimationCurve] = None, + ): + """ + Moves scroll position to either absolute `offset`, relative `delta` or jump to + the control with specified `key`. + + `offset` is an absolute value between minimum and maximum extents of a + scrollable control, for example: + + ```python + products.scroll_to(offset=100, duration=1000) + ``` + + `offset` could be a negative to scroll from the end of a scrollable. For + example, to scroll to the very end: + + ```python + products.scroll_to(offset=-1, duration=1000) + ``` + + `delta` allows moving scroll relatively to the current position. Use positive + `delta` to scroll forward and negative `delta` to scroll backward. For example, + to move scroll on 50 pixels forward: + + ```python + products.scroll_to(delta=50) + ``` + + `key` allows moving scroll position to a control with specified `key`. Most of + Flet controls have `key` property which is translated to Flutter as + "global key". `key` must be unique for the entire page/view. For example: + + ```python + import flet as ft + + def main(page: ft.Page): + cl = ft.Column( + spacing=10, + height=200, + width=200, + scroll=ft.ScrollMode.ALWAYS, + ) + for i in range(0, 50): + cl.controls.append(ft.Text(f"Text line {i}", key=str(i))) + + def scroll_to_key(e): + cl.scroll_to(key="20", duration=1000) + + page.add( + ft.Container(cl, border=ft.border.all(1)), + ft.ElevatedButton("Scroll to key '20'", on_click=scroll_to_key), + ) + + ft.app(main) + ``` + + Note: + `scroll_to()` method won't work with `ListView` and `GridView` controls + building their items dynamically. + + `duration` is scrolling animation duration in milliseconds. Defaults to `0` - + no animation. + + `curve` configures animation curve. Property value is [`AnimationCurve`](https://flet.dev/docs/reference/types/animationcurve) + enum. + Defaults to `AnimationCurve.EASE`. + """ + + await self._invoke_method_async( + "scroll_to", + { + "offset": offset, + "delta": delta, + "scroll_key": scroll_key, + "duration": duration, + "curve": curve, + }, + ) diff --git a/sdk/python/packages/flet/src/flet/controls/services/__init__.py b/sdk/python/packages/flet/src/flet/controls/services/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sdk/python/packages/flet/src/flet/controls/services/browser_context_menu.py b/sdk/python/packages/flet/src/flet/controls/services/browser_context_menu.py new file mode 100644 index 000000000..db27f0253 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/browser_context_menu.py @@ -0,0 +1,32 @@ +import asyncio +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.services.service import Service + +__all__ = ["BrowserContextMenu"] + + +@control("BrowserContextMenu") +class BrowserContextMenu(Service): + def __post_init__(self, ref): + Service.__post_init__(self, ref) + self.__disabled = False + + async def enable_async(self, wait_timeout: Optional[float] = None): + await self._invoke_method_async("enable_menu", timeout=wait_timeout) + self.__disabled = False + + def enable(self, wait_timeout: Optional[float] = None): + asyncio.create_task(self.enable_async(wait_timeout)) + + async def disable_async(self, wait_timeout: Optional[float] = None): + await self._invoke_method_async("disable_menu", timeout=wait_timeout) + self.__disabled = True + + def disable(self, wait_timeout: Optional[float] = None): + asyncio.create_task(self.disable_async(wait_timeout)) + + @property + def disabled(self): + return self.__disabled diff --git a/sdk/python/packages/flet/src/flet/controls/services/clipboard.py b/sdk/python/packages/flet/src/flet/controls/services/clipboard.py new file mode 100644 index 000000000..596fb9f74 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/clipboard.py @@ -0,0 +1,39 @@ +import asyncio +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.services.service import Service +from flet.controls.types import OptionalNumber + +__all__ = ["Clipboard"] + + +@control("Clipboard") +class Clipboard(Service): + def set(self, value: str, timeout: OptionalNumber = None) -> None: + """ + Get the last text value saved to a clipboard on a client side. + """ + asyncio.create_task(self.set_async(value, timeout=timeout)) + + async def set_async(self, value: str, timeout: OptionalNumber = None) -> None: + """ + Set clipboard data on a client side (user's web browser or a desktop), for + example: + + ```python + Example - TBD + ``` + """ + await self._invoke_method_async("set", {"data": value}, timeout=timeout) + + async def get_async(self, timeout: OptionalNumber = None) -> Optional[str]: + """ + Set clipboard data on a client side (user's web browser or a desktop), for + example: + + ```python + Example - TBD + ``` + """ + return await self._invoke_method_async("get", timeout=timeout) diff --git a/sdk/python/packages/flet/src/flet/controls/services/file_picker.py b/sdk/python/packages/flet/src/flet/controls/services/file_picker.py new file mode 100644 index 000000000..6e3ac7fdf --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/file_picker.py @@ -0,0 +1,286 @@ +import asyncio +from dataclasses import dataclass, field +from enum import Enum +from typing import Optional + +from flet.controls.base_control import control +from flet.controls.control_event import Event, OptionalEventHandler +from flet.controls.services.service import Service + +__all__ = [ + "FilePicker", + "FilePickerUploadEvent", + "FilePickerFileType", + "FilePickerUploadFile", + "FilePickerFile", +] + + +class FilePickerFileType(Enum): + """ + Defines the file types that can be selected using the + [FilePicker](https://flet.dev/docs/controls/filepicker). + """ + + ANY = "any" + """ + Allows any file type. + """ + + MEDIA = "media" + """ + A combination of `VIDEO` and `IMAGE`. + """ + + IMAGE = "image" + """ + Allows only image files. + """ + + VIDEO = "video" + """ + Allows only video files. + """ + + AUDIO = "audio" + """ + Allows only audio files. + """ + + CUSTOM = "custom" + """ + Allows only the custom file types specified in the + [allowed_extensions](https://flet.dev/docs/controls/filepicker) list. + """ + + +@dataclass +class FilePickerUploadFile: + upload_url: str + method: str = field(default="PUT") + id: Optional[int] = None + name: Optional[str] = None + + +@dataclass +class FilePickerFile: + id: int + """ + TBD + """ + + name: str + """ + File name without a path. + """ + + size: int + """ + File size in bytes. + """ + + path: Optional[str] = None + """ + Full path to a file. Works for desktop and mobile only. `None` on web. + """ + + +@dataclass +class FilePickerUploadEvent(Event["FilePicker"]): + file_name: str + """ + The name of the uploaded file. + """ + + progress: Optional[float] = None + """ + A value from `0.0` to `1.0` representing the progress of the upload. + """ + + error: Optional[str] = None + """ + An error message if the upload failed. + """ + + +@control("FilePicker") +class FilePicker(Service): + """ + A control that allows you to use the native file explorer to pick single + or multiple files, with extensions filtering support and upload. + + Online docs: https://flet.dev/docs/controls/filepicker + """ + + on_upload: OptionalEventHandler[FilePickerUploadEvent] = None + """ + Fires when a file upload progress is updated. + + Event object is an instance of + [FilePickerUploadEvent](https://flet.dev/docs/reference/types/filepickeruploadevent). + """ + + async def upload_async(self, files: list[FilePickerUploadFile]): + """ + Uploads selected files to specified upload URLs. + + Before calling `upload()` [pick_files()](https://flet.dev/docs/controls/filepicker#pick_files) + must be called, so the internal file picker selection is not empty. + + Method arguments: + - `files` - a list of + [FilePickerUploadFile](https://flet.dev/docs/reference/types/filepickeruploadfile). + Each item specifies which file to upload, and where (with PUT or POST). + """ + await self._invoke_method_async( + "upload", + {"files": files}, + ) + + def upload(self, files: list[FilePickerUploadFile]): + """ + Uploads selected files to specified upload URLs. + + Before calling `upload()` [pick_files()](https://flet.dev/docs/controls/filepicker#pick_files) + must be called, so the internal file picker selection is not empty. + + Method arguments: + - `files` - a list of + [FilePickerUploadFile](https://flet.dev/docs/reference/types/filepickeruploadfile). + Each item specifies which file to upload, and where (with PUT or POST). + """ + asyncio.create_task(self.upload_async(files)) + + async def get_directory_path_async( + self, + dialog_title: Optional[str] = None, + initial_directory: Optional[str] = None, + ) -> Optional[str]: + """ + Selects a directory and returns its absolute path. + + You could either set the following file picker properties or provide their + values in the method call: + + * `dialog_title` - the title of the dialog window. + * `initial_directory` - the initial directory where the dialog should open. + """ + return await self._invoke_method_async( + "get_directory_path", + { + "dialog_title": dialog_title, + "initial_directory": initial_directory, + }, + timeout=3600, + ) + + async def save_file_async( + self, + dialog_title: Optional[str] = None, + file_name: Optional[str] = None, + initial_directory: Optional[str] = None, + file_type: FilePickerFileType = FilePickerFileType.ANY, + allowed_extensions: Optional[list[str]] = None, + src_bytes: Optional[bytes] = None, + ) -> Optional[str]: + """ + Opens a save file dialog which lets the user select a file path and a file name + to save a file. + + This function does not actually save a file. It only opens the dialog to let + the user choose a location and file name. This function only returns the path + to this (non-existing) file in `FilePicker.result.path` property. + + This method is only available on desktop platforms (Linux, macOS & Windows). + + You could either set the following file picker properties or provide their + values in the method call: + + * `dialog_title` - the title of the dialog window. + * `file_name` - the default file name. + * `initial_directory` - the initial directory where the dialog should open. + * `file_type` - the allowed + [`FilePickerFileType`](https://flet.dev/docs/reference/types/filepickerfiletype). + * `allowed_extensions` - the allowed file extensions. Has effect only if + `file_type` is `FilePickerFileType.CUSTOM`. + + Info: + To save a file from the web, you don't need to use the FilePicker object. + + You can instead provides an API endpoint `/download/:filename` that returns the + file content, and then use + [`page.launch_url`](/docs/controls/page#launch_urlurl) to open the url, which + will trigger the browser's save file dialog. + + Take [FastAPI](https://flet.dev/docs/publish/web/dynamic-website#advanced-fastapi-scenarios) + as an example, you can use the following code to implement the endpoint: + + ```python + from fastapi import FastAPI, Response + from fastapi.responses import FileResponse + + app = flet_fastapi.app(main) + + @app.get("/download/{filename}") + def download(filename: str): + path = prepare_file(filename) + return FileResponse(path) + ``` + + and then use `page.launch_url("/download/myfile.txt")` to open the url, for + instance, when a button is clicked. + + ```python + ft.ElevatedButton( + "Download myfile", + on_click=lambda _: page.launch_url("/download/myfile.txt"), + ) + ``` + """ + return await self._invoke_method_async( + "save_file", + { + "dialog_title": dialog_title, + "file_name": file_name, + "initial_directory": initial_directory, + "file_type": file_type, + "allowed_extensions": allowed_extensions, + "src_bytes": src_bytes, + }, + timeout=3600, + ) + + async def pick_files_async( + self, + dialog_title: Optional[str] = None, + initial_directory: Optional[str] = None, + file_type: FilePickerFileType = FilePickerFileType.ANY, + allowed_extensions: Optional[list[str]] = None, + allow_multiple: bool = False, + ) -> list[FilePickerFile]: + """ + Retrieves the file(s) from the underlying platform. + + You could either set the following file picker properties or provide their + values in the method call: + + * `dialog_title` - the title of the dialog window. + * `initial_directory` - the initial directory where the dialog should open. + * `file_type` - the allowed + [`FilePickerFileType`](https://flet.dev/docs/reference/types/filepickerfiletype). + * `allowed_extensions` - the allowed file extensions. Has effect only if + `file_type` is `FilePickerFileType.CUSTOM`. + * `allow_multiple` - allow selecting multiple files. + """ + files = await self._invoke_method_async( + "pick_files", + { + "dialog_title": dialog_title, + "initial_directory": initial_directory, + "file_type": file_type, + "allowed_extensions": allowed_extensions, + "allow_multiple": allow_multiple, + }, + timeout=3600, + ) + return [FilePickerFile(**file) for file in files] diff --git a/sdk/python/packages/flet/src/flet/controls/services/haptic_feedback.py b/sdk/python/packages/flet/src/flet/controls/services/haptic_feedback.py new file mode 100644 index 000000000..1f59c65f7 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/haptic_feedback.py @@ -0,0 +1,77 @@ +import asyncio + +from flet.controls.base_control import control +from flet.controls.services.service import Service + +__all__ = ["HapticFeedback"] + + +@control("HapticFeedback") +class HapticFeedback(Service): + """ + Allows access to the haptic feedback interface on the device. + + It is non-visual and should be added to `page.services` list. + + Online docs: https://flet.dev/docs/controls/hapticfeedback + """ + + async def heavy_impact_async(self): + """ + Provides a haptic feedback corresponding a collision impact with a heavy mass. + """ + await self._invoke_method_async("heavy_impact") + + def heavy_impact(self): + """ + Provides a haptic feedback corresponding a collision impact with a heavy mass. + """ + asyncio.create_task(self.heavy_impact_async()) + + async def light_impact_async(self): + """ + Provides a haptic feedback corresponding a collision impact with a light mass. + """ + await self._invoke_method_async("light_impact") + + def light_impact(self): + """ + Provides a haptic feedback corresponding a collision impact with a light mass. + """ + asyncio.create_task(self.light_impact_async()) + + async def medium_impact_async(self): + """ + Provides a haptic feedback corresponding a collision impact with a medium mass. + """ + await self._invoke_method_async("medium_impact") + + def medium_impact(self): + """ + Provides a haptic feedback corresponding a collision impact with a medium mass. + """ + asyncio.create_task(self.medium_impact_async()) + + async def vibrate_async(self): + """ + Provides vibration haptic feedback to the user for a short duration. + """ + await self._invoke_method_async("vibrate") + + def vibrate(self): + """ + Provides vibration haptic feedback to the user for a short duration. + """ + asyncio.create_task(self.vibrate_async()) + + async def selection_click_async(self): + """ + TBD + """ + await self._invoke_method_async("selection_click") + + def selection_click(self): + """ + TBD + """ + asyncio.create_task(self.selection_click_async()) diff --git a/sdk/python/packages/flet/src/flet/controls/services/semantics_service.py b/sdk/python/packages/flet/src/flet/controls/services/semantics_service.py new file mode 100644 index 000000000..55e0c3493 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/semantics_service.py @@ -0,0 +1,85 @@ +import asyncio +from enum import Enum + +from flet.controls.base_control import control +from flet.controls.services.service import Service + +__all__ = ["SemanticsService", "Assertiveness"] + + +class Assertiveness(Enum): + POLITE = "polite" + ASSERTIVE = "assertive" + + +@control("SemanticsService") +class SemanticsService(Service): + """ + Allows access to the platform's accessibility services. + + Online docs: https://flet.dev/docs/controls/semanticsservice + """ + + async def announce_tooltip_async(self, message: str): + """ + Sends a semantic announcement of a tooltip. Currently honored on Android only. + + The provided `message` will be read by TalkBack. + """ + await self._invoke_method_async( + "announce_tooltip", arguments={"message": message} + ) + + def announce_tooltip(self, message: str): + """ + Sends a semantic announcement of a tooltip. Currently honored on Android only. + + The provided `message` will be read by TalkBack. + """ + asyncio.create_task(self.announce_tooltip_async(message)) + + async def announce_message_async( + self, + message: str, + rtl: bool = False, + assertiveness: Assertiveness = Assertiveness.POLITE, + ): + """ + Sends a semantic announcement with the given `message`. This should preferably + be used for announcements that are not seamlessly announced by the system as a + result of a UI state change. + + `rtl` is a boolean and indicates the text direction of the `message`. + + The `assertiveness` level of the announcement is only supported by the web + engine and has no effect on other platforms. Value is an `Assertiveness` enum + and can either be `Assertiveness.ASSERTIVE` or `Assertiveness.POLITE` (default). + """ + await self._invoke_method_async( + "announce_message", + arguments={ + "message": message, + "rtl": rtl, + "assertiveness": assertiveness, + }, + ) + + def announce_message( + self, + message: str, + rtl: bool = False, + assertiveness: Assertiveness = Assertiveness.POLITE, + ): + """ + Sends a semantic announcement with the given `message`. This should preferably + be used for announcements that are not seamlessly announced by the system as a + result of a UI state change. + + `rtl` is a boolean and indicates the text direction of the `message`. + + The `assertiveness` level of the announcement is only supported by the web + engine and has no effect on other platforms. Value is an `Assertiveness` enum + and can either be `Assertiveness.ASSERTIVE` or `Assertiveness.POLITE` (default). + """ + asyncio.create_task(self.announce_message_async(message, rtl, assertiveness)) + diff --git a/sdk/python/packages/flet/src/flet/controls/services/service.py b/sdk/python/packages/flet/src/flet/controls/services/service.py new file mode 100644 index 000000000..ed60c9a07 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/service.py @@ -0,0 +1,10 @@ +from dataclasses import dataclass + +from flet.controls.base_control import BaseControl + +__all__ = ["Service"] + + +@dataclass(kw_only=True) +class Service(BaseControl): + pass diff --git a/sdk/python/packages/flet/src/flet/controls/services/shake_detector.py b/sdk/python/packages/flet/src/flet/controls/services/shake_detector.py new file mode 100644 index 000000000..bf0fd6041 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/shake_detector.py @@ -0,0 +1,50 @@ +from flet.controls.base_control import control +from flet.controls.control_event import OptionalControlEventHandler +from flet.controls.services.service import Service +from flet.controls.types import Number + +__all__ = ["ShakeDetector"] + + +@control("ShakeDetector") +class ShakeDetector(Service): + """ + Detects phone shakes. + + It is non-visual and should be added to `page.services` list. + + Online docs: https://flet.dev/docs/controls/shakedetector + """ + + minimum_shake_count: int = 1 + """ + Number of shakes required before shake is triggered. + + Defaults to `1`. + """ + + shake_slop_time_ms: int = 500 + """ + Minimum time between shakes, in milliseconds. + + Defaults to `500`. + """ + + shake_count_reset_time_ms: int = 3000 + """ + Time, in milliseconds, before shake count resets. + + Defaults to `3000`. + """ + + shake_threshold_gravity: Number = 2.7 + """ + Shake detection threshold, in Gs. + + Defaults to `2.7`. + """ + + on_shake: OptionalControlEventHandler["ShakeDetector"] = None + """ + Triggers when the shake detected. + """ diff --git a/sdk/python/packages/flet/src/flet/controls/services/shared_preferences.py b/sdk/python/packages/flet/src/flet/controls/services/shared_preferences.py new file mode 100644 index 000000000..c51995998 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/shared_preferences.py @@ -0,0 +1,38 @@ +import asyncio +from typing import Any, List + +from flet.controls.base_control import control +from flet.controls.services.service import Service + +__all__ = ["SharedPreferences"] + + +@control("SharedPreferences") +class SharedPreferences(Service): + def set(self, key: str, value: Any) -> None: + asyncio.create_task(self.set_async(key, value)) + + async def set_async(self, key: str, value: Any) -> bool: + assert value is not None + return await self._invoke_method_async("set", {"key": key, "value": value}) + + async def get_async(self, key: str): + return await self._invoke_method_async("get", {"key": key}) + + async def contains_key_async(self, key: str) -> bool: + return await self._invoke_method_async("contains_key", {"key": key}) + + def remove(self, key: str) -> None: + asyncio.create_task(self.remove_async(key)) + + async def remove_async(self, key: str) -> bool: + return await self._invoke_method_async("remove", {"key": key}) + + async def get_keys_async(self, key_prefix: str) -> List[str]: + return await self._invoke_method_async("get_keys", {"key_prefix": key_prefix}) + + def clear(self) -> None: + asyncio.create_task(self.clear_async()) + + async def clear_async(self) -> bool: + return await self._invoke_method_async("clear") diff --git a/sdk/python/packages/flet/src/flet/controls/services/storage_paths.py b/sdk/python/packages/flet/src/flet/controls/services/storage_paths.py new file mode 100644 index 000000000..82c8b6401 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/storage_paths.py @@ -0,0 +1,81 @@ +from typing import List, Optional + +from flet.controls.base_control import control +from flet.controls.exceptions import FletUnsupportedPlatformException +from flet.controls.services.service import Service +from flet.controls.types import PagePlatform + +__all__ = ["StoragePaths"] + + +@control("StoragePaths") +class StoragePaths(Service): + async def get_application_cache_directory_async(self) -> str: + if self.page.web: + raise FletUnsupportedPlatformException( + "get_application_cache_directory is not supported on web" + ) + return await self._invoke_method_async("get_application_cache_directory") + + async def get_application_documents_directory_async(self) -> str: + if self.page.web: + raise FletUnsupportedPlatformException( + "get_application_documents_directory is not supported on web" + ) + return await self._invoke_method_async("get_application_documents_directory") + + async def get_application_support_directory_async(self) -> str: + if self.page.web: + raise FletUnsupportedPlatformException( + "get_application_support_directory is not supported on web" + ) + return await self._invoke_method_async("get_application_support_directory") + + async def get_downloads_directory_async(self) -> Optional[str]: + if self.page.web: + raise FletUnsupportedPlatformException( + "get_downloads_directory is not supported on web" + ) + return await self._invoke_method_async("get_downloads_directory") + + async def get_external_cache_directories_async(self) -> Optional[List[str]]: + if self.page.web or self.page.platform != PagePlatform.ANDROID: + raise FletUnsupportedPlatformException( + "get_external_cache_directories is supported only on Android" + ) + return await self._invoke_method_async("get_external_cache_directories") + + async def get_external_storage_directories_async(self) -> Optional[List[str]]: + if self.page.web or self.page.platform != PagePlatform.ANDROID: + raise FletUnsupportedPlatformException( + "get_external_storage_directories is supported only on Android" + ) + return await self._invoke_method_async("get_external_storage_directories") + + async def get_library_directory_async(self) -> str: + if self.page.web or not self.page.platform.is_apple(): + raise FletUnsupportedPlatformException( + "get_library_directory is supported only on iOS and macOS" + ) + return await self._invoke_method_async("get_library_directory") + + async def get_external_cache_directory_async(self) -> Optional[str]: + if self.page.web or self.page.platform != PagePlatform.ANDROID: + raise FletUnsupportedPlatformException( + "get_external_cache_directory is supported only on Android" + ) + return await self._invoke_method_async("get_external_cache_directory") + + async def get_temporary_directory_async(self) -> str: + if self.page.web: + raise FletUnsupportedPlatformException( + "get_temporary_directory is not supported on web" + ) + return await self._invoke_method_async("get_temporary_directory") + + async def get_console_log_filename_async(self) -> str: + if self.page.web: + raise FletUnsupportedPlatformException( + "get_console_log_filename is not supported on web" + ) + return await self._invoke_method_async("get_console_log_filename") diff --git a/sdk/python/packages/flet/src/flet/controls/services/url_launcher.py b/sdk/python/packages/flet/src/flet/controls/services/url_launcher.py new file mode 100644 index 000000000..190df39c9 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/services/url_launcher.py @@ -0,0 +1,53 @@ +import asyncio +from typing import Any, Optional + +from flet.controls.base_control import control +from flet.controls.services.service import Service + +__all__ = ["UrlLauncher"] + + +@control("UrlLauncher") +class UrlLauncher(Service): + async def launch_url_async( + self, + url: str, + web_window_name: Optional[str] = None, + web_popup_window: Optional[bool] = False, + window_width: Optional[int] = None, + window_height: Optional[int] = None, + ) -> None: + + await self._invoke_method_async( + "launch_url", + { + "url": url, + "web_window_name": web_window_name, + "web_popup_window": web_popup_window, + "window_width": window_width, + "window_height": window_height, + }, + ) + + def launch_url( + self, + url: str, + web_window_name: Optional[str] = None, + web_popup_window: Optional[bool] = False, + window_width: Optional[int] = None, + window_height: Optional[int] = None, + ) -> None: + asyncio.create_task( + self.launch_url_async( + url, web_window_name, web_popup_window, window_width, window_height + ) + ) + + async def can_launch_url_async(self, url: str) -> bool: + return await self._invoke_method_async("can_launch_url", {"url": url}) + + async def close_in_app_web_view_async(self) -> None: + await self._invoke_method_async("close_in_app_web_view") + + def close_in_app_web_view(self) -> None: + asyncio.create_task(self.close_in_app_web_view_async()) diff --git a/sdk/python/packages/flet/src/flet/core/session_storage.py b/sdk/python/packages/flet/src/flet/controls/session_storage.py similarity index 70% rename from sdk/python/packages/flet/src/flet/core/session_storage.py rename to sdk/python/packages/flet/src/flet/controls/session_storage.py index 8da7afe8b..ed5e32aaf 100644 --- a/sdk/python/packages/flet/src/flet/core/session_storage.py +++ b/sdk/python/packages/flet/src/flet/controls/session_storage.py @@ -1,10 +1,11 @@ -from typing import Any, Dict, List +from typing import Any + +__all__ = ["SessionStorage"] class SessionStorage: - def __init__(self, page): - self.__page = page - self.__store: Dict[str, Any] = {} + def __init__(self): + self.__store: dict[str, Any] = {} def set(self, key: str, value: Any): self.__store[key] = value @@ -18,7 +19,7 @@ def contains_key(self, key: str) -> bool: def remove(self, key: str): self.__store.pop(key) - def get_keys(self) -> List[str]: + def get_keys(self) -> list[str]: return list(self.__store.keys()) def clear(self): diff --git a/sdk/python/packages/flet/src/flet/core/template_route.py b/sdk/python/packages/flet/src/flet/controls/template_route.py similarity index 95% rename from sdk/python/packages/flet/src/flet/core/template_route.py rename to sdk/python/packages/flet/src/flet/controls/template_route.py index a60e8a867..03949be49 100644 --- a/sdk/python/packages/flet/src/flet/core/template_route.py +++ b/sdk/python/packages/flet/src/flet/controls/template_route.py @@ -2,6 +2,8 @@ import repath +__all__ = ["TemplateRoute"] + class TemplateRoute: def __init__(self, route: str) -> None: diff --git a/sdk/python/packages/flet/src/flet/controls/text_style.py b/sdk/python/packages/flet/src/flet/controls/text_style.py new file mode 100644 index 000000000..4900cafc0 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/text_style.py @@ -0,0 +1,291 @@ +from dataclasses import dataclass +from enum import Enum, IntFlag +from typing import Optional + +from flet.controls.box import ShadowValue +from flet.controls.painting import Paint +from flet.controls.types import FontWeight, OptionalColorValue, OptionalNumber + +__all__ = [ + "TextOverflow", + "TextBaseline", + "TextThemeStyle", + "TextDecoration", + "TextDecorationStyle", + "TextStyle", + "StrutStyle", + "OptionalTextStyle", + "OptionalStrutStyle", + "OptionalTextOverflow", + "OptionalTextBaseline", + "OptionalTextThemeStyle", + "OptionalTextDecoration", + "OptionalTextDecorationStyle", +] + + +class TextOverflow(Enum): + CLIP = "clip" + ELLIPSIS = "ellipsis" + FADE = "fade" + VISIBLE = "visible" + + +class TextBaseline(Enum): + ALPHABETIC = "alphabetic" + IDEOGRAPHIC = "ideographic" + + +class TextThemeStyle(Enum): + DISPLAY_LARGE = "displayLarge" + DISPLAY_MEDIUM = "displayMedium" + DISPLAY_SMALL = "displaySmall" + HEADLINE_LARGE = "headlineLarge" + HEADLINE_MEDIUM = "headlineMedium" + HEADLINE_SMALL = "headlineSmall" + TITLE_LARGE = "titleLarge" + TITLE_MEDIUM = "titleMedium" + TITLE_SMALL = "titleSmall" + LABEL_LARGE = "labelLarge" + LABEL_MEDIUM = "labelMedium" + LABEL_SMALL = "labelSmall" + BODY_LARGE = "bodyLarge" + BODY_MEDIUM = "bodyMedium" + BODY_SMALL = "bodySmall" + + +class TextDecoration(IntFlag): + """ + TBD + """ + + NONE = 0 + """ + Do not draw a decoration. + """ + + UNDERLINE = 1 + """ + Draw a line underneath each line of text. + """ + + OVERLINE = 2 + """ + Draw a line above each line of text. + """ + + LINE_THROUGH = 4 + """ + Draw a line through each line of text. + """ + + +class TextDecorationStyle(Enum): + """ + TBD + """ + + SOLID = "solid" + """ + Draw a solid line. + """ + + DOUBLE = "double" + """ + Draw two lines. + """ + + DOTTED = "dotted" + """ + Draw a dotted line. + """ + + DASHED = "dashed" + """ + Draw a dashed line. + """ + + WAVY = "wavy" + """ + Draw a sinusoidal line. + """ + + +@dataclass +class TextStyle: + """ + A style describing how to format and paint text. It has the following properties: + """ + + size: OptionalNumber = None + """ + The size of glyphs (in logical pixels) to use when painting the text. + + Defaults to `14`. + """ + + height: OptionalNumber = None + """ + The height of this text span, as a multiple of the font size. + + See detailed explanation here: + https://api.flutter.dev/flutter/painting/TextStyle/height.html + """ + + weight: Optional[FontWeight] = None + """ + Value is of type https://flet.dev/docs/reference/types/fontweight and defaults to + `FontWeight.NORMAL`. + """ + + italic: Optional[bool] = None + """ + `True` to use italic typeface. + """ + + decoration: Optional[TextDecoration] = None + """ + The decorations to paint near the text (e.g., an underline). + + Value is of type https://flet.dev/docs/reference/types/textdecoration. + """ + + decoration_color: OptionalColorValue = None + """ + The https://flet.dev/docs/reference/colors in which to paint the text decorations. + """ + + decoration_thickness: OptionalNumber = None + """ + The thickness of the decoration stroke as a multiplier of the thickness defined by + the font. + """ + + decoration_style: "OptionalTextDecorationStyle" = None + """ + The style in which to paint the text decorations (e.g., dashed). + + Value is of type https://flet.dev/docs/reference/types/textdecorationstyle and + defaults to `TextDecorationStyle.SOLID`. + """ + + font_family: Optional[str] = None + """ + See https://flet.dev/docs/controls/text#font_family. + """ + + color: OptionalColorValue = None + """ + Text foreground https://flet.dev/docs/reference/colors. + """ + + bgcolor: OptionalColorValue = None + """ + Text background https://flet.dev/docs/reference/colors. + """ + + shadow: Optional[ShadowValue] = None + """ + The value of this property is a single instance or a list of + https://flet.dev/docs/reference/types/boxshadow class instances. + """ + + foreground: Optional[Paint] = None + """ + The paint drawn as a foreground for the text. + + Value is of type https://flet.dev/docs/reference/types/paint. + """ + + letter_spacing: OptionalNumber = None + """ + The amount of space (in logical pixels) to add between each letter. A negative + value can be used to bring the letters closer. + """ + + word_spacing: OptionalNumber = None + """ + The amount of space (in logical pixels) to add at each sequence of white-space + (i.e. between each word). A negative value can be used to bring the words closer. + """ + + overflow: Optional[TextOverflow] = None + """ + How visual text overflow should be handled. + + Value is of type https://flet.dev/docs/reference/types/textoverflow. + """ + + baseline: Optional[TextBaseline] = None + """ + The common baseline that should be aligned between this text span and its parent + text span, or, for the root text spans, with the line box. + + Value is of type https://flet.dev/docs/reference/types/textbaseline. + """ + + +@dataclass +class StrutStyle: + """ + TBD + """ + + size: OptionalNumber = None + """ + The size of text (in logical pixels) to use when getting metrics from the font. + + Defaults to `14`. + """ + + height: OptionalNumber = None + """ + The minimum height of the strut, as a multiple of `size`. + + See detailed explanation here: + https://api.flutter.dev/flutter/painting/StrutStyle/height.html + """ + + weight: Optional[FontWeight] = None + """ + The typeface thickness to use when calculating the strut. + + Value is of type https://flet.dev/docs/reference/types/fontweight and defaults to + `FontWeight.W_400`. + """ + + italic: Optional[bool] = None + """ + `True` to use italic typeface. + + Defaults to `False`. + """ + + font_family: Optional[str] = None + """ + See https://flet.dev/docs/controls/text#font_family. + """ + + leading: OptionalNumber = None + """ + The amount of additional space to place between lines when rendering text. + + Defaults to using the font-specified leading value. + """ + + force_strut_height: Optional[bool] = None + """ + Whether the strut height should be forced. + + Defaults to `False`. + """ + + +# Typing +OptionalTextStyle = Optional[TextStyle] +OptionalStrutStyle = Optional[StrutStyle] +OptionalTextOverflow = Optional[TextOverflow] +OptionalTextBaseline = Optional[TextBaseline] +OptionalTextThemeStyle = Optional[TextThemeStyle] +OptionalTextDecoration = Optional[TextDecoration] +OptionalTextDecorationStyle = Optional[TextDecorationStyle] diff --git a/sdk/python/packages/flet/src/flet/controls/theme.py b/sdk/python/packages/flet/src/flet/controls/theme.py new file mode 100644 index 000000000..58c78225c --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/theme.py @@ -0,0 +1,1048 @@ +from dataclasses import dataclass, field +from enum import Enum +from typing import Optional + +from flet.controls.alignment import Alignment +from flet.controls.border import BorderSide, OptionalBorderSide +from flet.controls.border_radius import OptionalBorderRadiusValue +from flet.controls.box import BoxConstraints, BoxDecoration, BoxShadow +from flet.controls.buttons import ButtonStyle, OutlinedBorder +from flet.controls.control_state import ControlStateValue +from flet.controls.duration import OptionalDurationValue +from flet.controls.margin import OptionalMarginValue +from flet.controls.material.menu_bar import MenuStyle +from flet.controls.material.navigation_bar import NavigationBarLabelBehavior +from flet.controls.material.navigation_rail import NavigationRailLabelType +from flet.controls.material.popup_menu_button import PopupMenuPosition +from flet.controls.material.slider import SliderInteraction +from flet.controls.material.snack_bar import DismissDirection, SnackBarBehavior +from flet.controls.material.textfield import TextCapitalization +from flet.controls.material.tooltip import TooltipTriggerMode +from flet.controls.padding import OptionalPaddingValue, PaddingValue +from flet.controls.geometry import Size +from flet.controls.text_style import OptionalTextStyle, TextStyle +from flet.controls.transform import OffsetValue +from flet.controls.types import ( + Brightness, + ClipBehavior, + ColorValue, + IconValue, + Locale, + MainAxisAlignment, + MouseCursor, + NotchShape, + OptionalBool, + OptionalColorValue, + OptionalNumber, + StrokeCap, + TextAlign, + VisualDensity, +) + + +class PageTransitionTheme(Enum): + NONE = "none" + FADE_UPWARDS = "fadeUpwards" + OPEN_UPWARDS = "openUpwards" + ZOOM = "zoom" + CUPERTINO = "cupertino" + PREDICTIVE = "predictive" + FADE_FORWARDS = "fadeForwards" + + +@dataclass +class PageTransitionsTheme: + android: Optional[PageTransitionTheme] = None + ios: Optional[PageTransitionTheme] = None + linux: Optional[PageTransitionTheme] = None + macos: Optional[PageTransitionTheme] = None + windows: Optional[PageTransitionTheme] = None + + +@dataclass +class ColorScheme: + """ + A set of more than 40 colors based on the [Material spec](https://m3.material.io/styles/color/the-color-system/color-roles) + that can be used to configure the color properties of most components. + Read more about color schemes in [here](https://api.flutter.dev/flutter/material/ColorScheme-class.html). + """ + primary: OptionalColorValue = None + """ + The color displayed most frequently across your app’s screens and components. + """ + on_primary: OptionalColorValue = None + """ + A color that's clearly legible when drawn on `primary`. + """ + primary_container: OptionalColorValue = None + """ + A color used for elements needing less emphasis than `primary`. + """ + on_primary_container: OptionalColorValue = None + """ + A color that's clearly legible when drawn on `primary_container`. + """ + secondary: OptionalColorValue = None + """ + An accent color used for less prominent components in the UI, such as filter chips, + while expanding the opportunity for color expression. + """ + on_secondary: OptionalColorValue = None + """ + A color that's clearly legible when drawn on `secondary`. + """ + secondary_container: OptionalColorValue = None + """ + A color used for elements needing less emphasis than `secondary`. + """ + on_secondary_container: OptionalColorValue = None + """ + A color that's clearly legible when drawn on `secondary_container`. + """ + tertiary: OptionalColorValue = None + """ + A color used as a contrasting accent that can balance `primary` and `secondary` + colors or bring heightened attention to an element, such as an input field. + """ + on_tertiary: OptionalColorValue = None + """ + A color that's clearly legible when drawn on `tertiary`. + """ + tertiary_container: OptionalColorValue = None + """ + A color used for elements needing less emphasis than `tertiary`. + """ + on_tertiary_container: OptionalColorValue = None + """ + A color that's clearly legible when drawn on `tertiary_container`. + """ + error: OptionalColorValue = None + """ + The color to use for input validation errors, e.g. for `TextField.error_text`. + """ + on_error: OptionalColorValue = None + """ + A color that's clearly legible when drawn on `error`. + """ + error_container: OptionalColorValue = None + """ + A color used for error elements needing less emphasis than `error`. + """ + on_error_container: OptionalColorValue = None + """ + A color that's clearly legible when drawn on `error_container`. + """ + background: OptionalColorValue = None + """ + A color that typically appears behind scrollable content. + """ + on_background: OptionalColorValue = None + """ + A color that's clearly legible when drawn on `background`. + """ + surface: OptionalColorValue = None + """ + The background color for widgets like `Card`. + """ + on_surface: OptionalColorValue = None + """ + A color that's clearly legible when drawn on `surface`. + """ + surface_variant: OptionalColorValue = None + """ + A color variant of `surface` that can be used for differentiation against a + component using `surface`. + """ + on_surface_variant: OptionalColorValue = None + """ + A color that's clearly legible when drawn on `surface_variant`. + """ + outline: OptionalColorValue = None + """ + A utility color that creates boundaries and emphasis to improve usability. + """ + outline_variant: OptionalColorValue = None + """ + A utility color that creates boundaries for decorative elements when a 3:1 contrast + isn’t required, such as for dividers or decorative elements. + """ + shadow: OptionalColorValue = None + """ + A color use to paint the drop shadows of elevated components. + """ + scrim: OptionalColorValue = None + """ + A color use to paint the scrim around of modal components. + """ + inverse_surface: OptionalColorValue = None + """ + A surface color used for displaying the reverse of what’s seen in the surrounding + UI, for example in a `SnackBar` to bring attention to an alert. + """ + on_inverse_surface: OptionalColorValue = None + """ + A color that's clearly legible when drawn on `inverse_surface`. + """ + inverse_primary: OptionalColorValue = None + """ + An accent color used for displaying a highlight color on `inverse_surface` + backgrounds, like button text in a `SnackBar`. + """ + surface_tint: OptionalColorValue = None + """ + A color used as an overlay on a surface color to indicate a component's elevation. + """ + on_primary_fixed: OptionalColorValue = None + """ + A color that is used for text and icons that exist on top of elements having + `primary_fixed` color. + """ + on_secondary_fixed: OptionalColorValue = None + """ + A color that is used for text and icons that exist on top of elements having + `secondary_fixed` color. + """ + on_tertiary_fixed: OptionalColorValue = None + """ + A color that is used for text and icons that exist on top of elements having + `tertiary_fixed` color. + """ + on_primary_fixed_variant: OptionalColorValue = None + """ + A color that provides a lower-emphasis option for text and icons than + `on_primary_fixed`. + """ + on_secondary_fixed_variant: OptionalColorValue = None + """ + A color that provides a lower-emphasis option for text and icons than + `on_secondary_fixed`. + """ + on_tertiary_fixed_variant: OptionalColorValue = None + """ + A color that provides a lower-emphasis option for text and icons than + `on_tertiary_fixed`. + """ + primary_fixed: OptionalColorValue = None + """ + A substitute for `primary_container` that's the same color for the dark and light + themes. + """ + secondary_fixed: OptionalColorValue = None + """ + A substitute for `secondary_container` that's the same color for the dark and light + themes. + """ + tertiary_fixed: OptionalColorValue = None + """ + A substitute for `tertiary_container` that's the same color for dark and light + themes. + """ + primary_fixed_dim: OptionalColorValue = None + """ + A color used for elements needing more emphasis than `primary_fixed`. + """ + secondary_fixed_dim: OptionalColorValue = None + """ + A color used for elements needing more emphasis than `secondary_fixed`. + """ + surface_bright: OptionalColorValue = None + """ + A color that's always the lightest in the dark or light theme. + """ + surface_container: OptionalColorValue = None + surface_container_high: OptionalColorValue = None + surface_container_low: OptionalColorValue = None + surface_container_lowest: OptionalColorValue = None + surface_dim: OptionalColorValue = None + tertiary_fixed_dim: OptionalColorValue = None + + +@dataclass +class TextTheme: + body_large: OptionalTextStyle = None + body_medium: OptionalTextStyle = None + body_small: OptionalTextStyle = None + display_large: OptionalTextStyle = None + display_medium: OptionalTextStyle = None + display_small: OptionalTextStyle = None + headline_large: OptionalTextStyle = None + headline_medium: OptionalTextStyle = None + headline_small: OptionalTextStyle = None + label_large: OptionalTextStyle = None + label_medium: OptionalTextStyle = None + label_small: OptionalTextStyle = None + title_large: OptionalTextStyle = None + title_medium: OptionalTextStyle = None + title_small: OptionalTextStyle = None + + +@dataclass +class ScrollbarTheme: + thumb_visibility: Optional[ControlStateValue[bool]] = None + thickness: Optional[ControlStateValue[OptionalNumber]] = None + track_visibility: Optional[ControlStateValue[bool]] = None + radius: OptionalNumber = None + thumb_color: Optional[ControlStateValue[ColorValue]] = None + track_color: Optional[ControlStateValue[ColorValue]] = None + track_border_color: Optional[ControlStateValue[ColorValue]] = None + cross_axis_margin: OptionalNumber = None + main_axis_margin: OptionalNumber = None + min_thumb_length: OptionalNumber = None + interactive: OptionalBool = None + + +@dataclass +class TabsTheme: + divider_color: OptionalColorValue = None + indicator_border_radius: OptionalBorderRadiusValue = None + indicator_border_side: OptionalBorderSide = None + indicator_padding: OptionalPaddingValue = None + indicator_color: OptionalColorValue = None + indicator_tab_size: OptionalBool = None + label_color: OptionalColorValue = None + unselected_label_color: OptionalColorValue = None + overlay_color: Optional[ControlStateValue[ColorValue]] = None + mouse_cursor: Optional[ControlStateValue[MouseCursor]] = None + label_padding: OptionalPaddingValue = None + label_text_style: OptionalTextStyle = None + unselected_label_text_style: OptionalTextStyle = None + + +@dataclass +class SystemOverlayStyle: + status_bar_color: OptionalColorValue = None + system_navigation_bar_color: OptionalColorValue = None + system_navigation_bar_divider_color: OptionalColorValue = None + enforce_system_navigation_bar_contrast: OptionalBool = None + enforce_system_status_bar_contrast: OptionalBool = None + system_navigation_bar_icon_brightness: Optional[Brightness] = None + status_bar_brightness: Optional[Brightness] = None + status_bar_icon_brightness: Optional[Brightness] = None + + +@dataclass +class DialogTheme: + bgcolor: OptionalColorValue = None + shadow_color: OptionalColorValue = None + surface_tint_color: OptionalColorValue = None + icon_color: OptionalColorValue = None + elevation: OptionalNumber = None + shape: Optional[OutlinedBorder] = None + title_text_style: OptionalTextStyle = None + content_text_style: OptionalTextStyle = None + alignment: Optional[Alignment] = None + actions_padding: OptionalPaddingValue = None + clip_behavior: Optional[ClipBehavior] = None + barrier_color: OptionalColorValue = None + inset_padding: OptionalPaddingValue = None + + +@dataclass +class ElevatedButtonTheme: + bgcolor: OptionalColorValue = None + foreground_color: OptionalColorValue = None + icon_color: OptionalColorValue = None + shadow_color: OptionalColorValue = None + disabled_bgcolor: OptionalColorValue = None + disabled_foreground_color: OptionalColorValue = None + disabled_icon_color: OptionalColorValue = None + overlay_color: OptionalColorValue = None + surface_tint_color: OptionalColorValue = None + elevation: OptionalNumber = None + padding: OptionalPaddingValue = None + enable_feedback: OptionalBool = None + disabled_mouse_cursor: Optional[MouseCursor] = None + enabled_mouse_cursor: Optional[MouseCursor] = None + shape: Optional[OutlinedBorder] = None + text_style: OptionalTextStyle = None + visual_density: Optional[VisualDensity] = None + border_side: OptionalBorderSide = None + animation_duration: OptionalDurationValue = None + alignment: Optional[Alignment] = None + icon_size: OptionalNumber = None + fixed_size: Optional[Size] = None + maximum_size: Optional[Size] = None + minimum_size: Optional[Size] = None + + +@dataclass +class OutlinedButtonTheme(ElevatedButtonTheme): + pass + + +@dataclass +class TextButtonTheme(ElevatedButtonTheme): + pass + + +@dataclass +class FilledButtonTheme(ElevatedButtonTheme): + pass + + +@dataclass +class IconButtonTheme: + # from ElevatedButtonTheme (excluding icon_color, disabled_icon_color, text_style) + bgcolor: OptionalColorValue = None + foreground_color: OptionalColorValue = None + shadow_color: OptionalColorValue = None + disabled_bgcolor: OptionalColorValue = None + disabled_foreground_color: OptionalColorValue = None + overlay_color: OptionalColorValue = None + surface_tint_color: OptionalColorValue = None + elevation: OptionalNumber = None + padding: OptionalPaddingValue = None + enable_feedback: OptionalBool = None + disabled_mouse_cursor: Optional[MouseCursor] = None + enabled_mouse_cursor: Optional[MouseCursor] = None + shape: Optional[OutlinedBorder] = None + visual_density: Optional[VisualDensity] = None + border_side: OptionalBorderSide = None + animation_duration: OptionalDurationValue = None + alignment: Optional[Alignment] = None + icon_size: OptionalNumber = None + fixed_size: Optional[Size] = None + maximum_size: Optional[Size] = None + minimum_size: Optional[Size] = None + # Icon Button Theme + focus_color: OptionalColorValue = None + highlight_color: OptionalColorValue = None + hover_color: OptionalColorValue = None + + +@dataclass +class BottomSheetTheme: + bgcolor: OptionalColorValue = None + shadow_color: OptionalColorValue = None + surface_tint_color: OptionalColorValue = None + drag_handle_color: OptionalColorValue = None + elevation: OptionalNumber = None + shape: Optional[OutlinedBorder] = None + show_drag_handle: OptionalBool = None + modal_bgcolor: OptionalColorValue = None + modal_elevation: OptionalNumber = None + clip_behavior: Optional[ClipBehavior] = None + size_constraints: Optional[BoxConstraints] = None + modal_barrier_color: OptionalColorValue = None + + +@dataclass +class CardTheme: + color: OptionalColorValue = None + shadow_color: OptionalColorValue = None + surface_tint_color: OptionalColorValue = None + elevation: OptionalNumber = None + shape: Optional[OutlinedBorder] = None + clip_behavior: Optional[ClipBehavior] = None + margin: OptionalMarginValue = None + + +@dataclass +class ChipTheme: + color: Optional[ControlStateValue[ColorValue]] = None + bgcolor: OptionalColorValue = None + shadow_color: OptionalColorValue = None + surface_tint_color: OptionalColorValue = None + disabled_color: OptionalColorValue = None + selected_color: OptionalColorValue = None + checkmark_color: OptionalColorValue = None + delete_icon_color: OptionalColorValue = None + secondary_selected_color: OptionalColorValue = None + selected_shadow_color: OptionalColorValue = None + elevation: OptionalNumber = None + click_elevation: OptionalNumber = None + shape: Optional[OutlinedBorder] = None + padding: OptionalPaddingValue = None + label_padding: OptionalPaddingValue = None + label_text_style: OptionalTextStyle = None + secondary_label_text_style: OptionalTextStyle = None + border_side: OptionalBorderSide = None + brightness: Optional[Brightness] = None + show_checkmark: OptionalBool = None + avatar_constraints: Optional[BoxConstraints] = None + delete_icon_size_constraints: Optional[BoxConstraints] = None + + +@dataclass +class FloatingActionButtonTheme: + bgcolor: OptionalColorValue = None + hover_color: OptionalColorValue = None + focus_color: OptionalColorValue = None + foreground_color: OptionalColorValue = None + splash_color: OptionalColorValue = None + elevation: OptionalNumber = None + focus_elevation: OptionalNumber = None + hover_elevation: OptionalNumber = None + highlight_elevation: OptionalNumber = None + disabled_elevation: OptionalNumber = None + shape: Optional[OutlinedBorder] = None + enable_feedback: OptionalBool = None + extended_padding: OptionalPaddingValue = None + extended_text_style: OptionalTextStyle = None + extended_icon_label_spacing: OptionalNumber = None + extended_size_constraints: Optional[BoxConstraints] = None + size_constraints: Optional[BoxConstraints] = None + large_size_constraints: Optional[BoxConstraints] = None + small_size_constraints: Optional[BoxConstraints] = None + + +@dataclass +class NavigationRailTheme: + bgcolor: OptionalColorValue = None + indicator_color: OptionalColorValue = None + elevation: OptionalNumber = None + indicator_shape: Optional[OutlinedBorder] = None + unselected_label_text_style: OptionalTextStyle = None + selected_label_text_style: OptionalTextStyle = None + label_type: Optional[NavigationRailLabelType] = None + min_width: OptionalNumber = None + min_extended_width: OptionalNumber = None + group_alignment: OptionalNumber = None + use_indicator: OptionalBool = None + + +@dataclass +class AppBarTheme: + """ + Customizes the appearance of https://flet.dev/docs/controls/appbar across the app. + """ + + color: OptionalColorValue = None + """ + Overrides the default value of `AppBar.color` in all descendant `AppBar` controls. + """ + + bgcolor: OptionalColorValue = None + """ + Overrides the default value of `AppBar.bgcolor` in all descendant `AppBar` controls. + """ + + shadow_color: OptionalColorValue = None + """ + Overrides the default value of `AppBar.shadow_color` in all descendant `AppBar` + controls. + """ + + surface_tint_color: OptionalColorValue = None + """ + Overrides the default value of `AppBar.surface_tint_color` in all descendant + `AppBar` controls. + """ + + foreground_color: OptionalColorValue = None + """ + TBD + """ + + elevation: OptionalNumber = None + """ + Overrides the default value of `AppBar.elevation` in all descendant `AppBar` + controls. + """ + + shape: Optional[OutlinedBorder] = None + """ + Overrides the default value of `AppBar.shape` in all descendant `AppBar` controls. + """ + + title_text_style: OptionalTextStyle = None + """ + Overrides the default value of `AppBar.title_text_style` in all descendant + `AppBar` controls. + """ + + toolbar_text_style: OptionalTextStyle = None + """ + Overrides the default value of `AppBar.toolbar_text_style` in all descendant + `AppBar` controls. + """ + + center_title: OptionalBool = None + """ + Overrides the default value of `AppBar.center_title` in all descendant `AppBar` + controls. + """ + + title_spacing: OptionalNumber = None + """ + Overrides the default value of `AppBar.title_spacing` in all descendant `AppBar` + controls. + """ + + scroll_elevation: OptionalNumber = None + """ + Overrides the default value of `AppBar.scroll_elevation` in all descendant + `AppBar` controls. + """ + + toolbar_height: OptionalNumber = None + """ + Overrides the default value of `AppBar.toolbar_height` in all descendant `AppBar` + controls. + """ + + actions_padding: OptionalPaddingValue = None + """ + TBD + """ + + +@dataclass +class BottomAppBarTheme: + color: OptionalColorValue = None + shadow_color: OptionalColorValue = None + surface_tint_color: OptionalColorValue = None + elevation: OptionalNumber = None + height: OptionalNumber = None + padding: OptionalPaddingValue = None + shape: Optional[NotchShape] = None + + +@dataclass +class RadioTheme: + fill_color: Optional[ControlStateValue[ColorValue]] = None + overlay_color: Optional[ControlStateValue[ColorValue]] = None + splash_radius: OptionalNumber = None + height: OptionalNumber = None + visual_density: Optional[VisualDensity] = None + mouse_cursor: Optional[ControlStateValue[MouseCursor]] = None + + +@dataclass +class CheckboxTheme: + overlay_color: Optional[ControlStateValue[ColorValue]] = None + check_color: Optional[ControlStateValue[ColorValue]] = None + fill_color: Optional[ControlStateValue[ColorValue]] = None + splash_radius: OptionalNumber = None + border_side: OptionalBorderSide = None + visual_density: Optional[VisualDensity] = None + shape: Optional[OutlinedBorder] = None + mouse_cursor: Optional[ControlStateValue[MouseCursor]] = None + + +@dataclass +class BadgeTheme: + bgcolor: OptionalColorValue = None + text_color: OptionalColorValue = None + small_size: OptionalNumber = None + large_size: OptionalNumber = None + alignment: Optional[Alignment] = None + padding: OptionalPaddingValue = None + offset: Optional[OffsetValue] = None + text_style: OptionalTextStyle = None + + +@dataclass +class SwitchTheme: + thumb_color: Optional[ControlStateValue[ColorValue]] = None + track_color: Optional[ControlStateValue[ColorValue]] = None + overlay_color: Optional[ControlStateValue[ColorValue]] = None + track_outline_color: Optional[ControlStateValue[ColorValue]] = None + thumb_icon: Optional[ControlStateValue[str]] = None + track_outline_width: Optional[ControlStateValue[OptionalNumber]] = None + splash_radius: OptionalNumber = None + mouse_cursor: Optional[ControlStateValue[MouseCursor]] = None + padding: OptionalPaddingValue = None + + +@dataclass +class DividerTheme: + color: OptionalColorValue = None + thickness: OptionalNumber = None + space: OptionalNumber = None + leading_indent: OptionalNumber = None + trailing_indent: OptionalNumber = None + + +@dataclass +class SnackBarTheme: + bgcolor: OptionalColorValue = None + action_text_color: OptionalColorValue = None + action_bgcolor: OptionalColorValue = None + close_icon_color: OptionalColorValue = None + disabled_action_text_color: OptionalColorValue = None + disabled_action_bgcolor: OptionalColorValue = None + elevation: OptionalNumber = None + content_text_style: OptionalTextStyle = None + width: OptionalNumber = None + alignment: Optional[Alignment] = None + show_close_icon: OptionalBool = None + dismiss_direction: Optional[DismissDirection] = None + behavior: Optional[SnackBarBehavior] = None + shape: Optional[OutlinedBorder] = None + inset_padding: OptionalPaddingValue = None + action_overflow_threshold: OptionalNumber = None + + +@dataclass +class BannerTheme: + bgcolor: OptionalColorValue = None + surface_tint_color: OptionalColorValue = None + shadow_color: OptionalColorValue = None + divider_color: OptionalColorValue = None + padding: OptionalPaddingValue = None + leading_padding: OptionalPaddingValue = None + elevation: OptionalNumber = None + content_text_style: OptionalTextStyle = None + + +@dataclass +class DatePickerTheme: + bgcolor: OptionalColorValue = None + surface_tint_color: OptionalColorValue = None + shadow_color: OptionalColorValue = None + divider_color: OptionalColorValue = None + header_bgcolor: OptionalColorValue = None + today_bgcolor: Optional[ControlStateValue[ColorValue]] = None + day_bgcolor: Optional[ControlStateValue[ColorValue]] = None + day_overlay_color: Optional[ControlStateValue[ColorValue]] = None + day_foreground_color: Optional[ControlStateValue[ColorValue]] = None + elevation: OptionalNumber = None + range_picker_elevation: OptionalNumber = None + day_text_style: OptionalTextStyle = None + weekday_text_style: OptionalTextStyle = None + year_text_style: OptionalTextStyle = None + shape: Optional[OutlinedBorder] = None + cancel_button_style: Optional[ButtonStyle] = None + confirm_button_style: Optional[ButtonStyle] = None + header_foreground_color: OptionalColorValue = None + header_headline_text_style: OptionalTextStyle = None + header_help_text_style: OptionalTextStyle = None + range_picker_bgcolor: OptionalColorValue = None + range_picker_header_bgcolor: OptionalColorValue = None + range_picker_header_foreground_color: OptionalColorValue = None + today_foreground_color: Optional[ControlStateValue[ColorValue]] = None + range_picker_shape: Optional[OutlinedBorder] = None + range_picker_header_help_text_style: OptionalTextStyle = None + range_picker_header_headline_text_style: OptionalTextStyle = None + range_picker_surface_tint_color: OptionalColorValue = None + range_selection_bgcolor: OptionalColorValue = None + range_selection_overlay_color: Optional[ControlStateValue[ColorValue]] = None + today_border_side: OptionalBorderSide = None + year_bgcolor: Optional[ControlStateValue[ColorValue]] = None + year_foreground_color: Optional[ControlStateValue[ColorValue]] = None + year_overlay_color: Optional[ControlStateValue[ColorValue]] = None + day_shape: Optional[ControlStateValue[OutlinedBorder]] = None + locale: Optional[Locale] = None + + +@dataclass +class TimePickerTheme: + bgcolor: OptionalColorValue = None + day_period_color: OptionalColorValue = None + day_period_text_color: OptionalColorValue = None + dial_bgcolor: OptionalColorValue = None + dial_hand_color: OptionalColorValue = None + dial_text_color: OptionalColorValue = None + entry_mode_icon_color: OptionalColorValue = None + hour_minute_color: OptionalColorValue = None + hour_minute_text_color: OptionalColorValue = None + day_period_button_style: Optional[ButtonStyle] = None + cancel_button_style: Optional[ButtonStyle] = None + confirm_button_style: Optional[ButtonStyle] = None + day_period_text_style: OptionalTextStyle = None + dial_text_style: OptionalTextStyle = None + help_text_style: OptionalTextStyle = None + hour_minute_text_style: OptionalTextStyle = None + elevation: OptionalNumber = None + shape: Optional[OutlinedBorder] = None + day_period_shape: Optional[OutlinedBorder] = None + hour_minute_shape: Optional[OutlinedBorder] = None + day_period_border_side: OptionalBorderSide = None + padding: OptionalPaddingValue = None + time_selector_separator_color: Optional[ControlStateValue[ColorValue]] = None + time_selector_separator_text_style: Optional[ControlStateValue[TextStyle]] = None + + +@dataclass +class DropdownMenuTheme: + menu_style: Optional[MenuStyle] = None + text_style: OptionalTextStyle = None + + +@dataclass +class ListTileTheme: + icon_color: OptionalColorValue = None + text_color: OptionalColorValue = None + bgcolor: OptionalColorValue = None + selected_tile_color: OptionalColorValue = None + selected_color: OptionalColorValue = None + is_three_line: OptionalBool = None + enable_feedback: OptionalBool = None + dense: OptionalBool = None + shape: Optional[OutlinedBorder] = None + visual_density: Optional[VisualDensity] = None + content_padding: OptionalPaddingValue = None + min_vertical_padding: OptionalPaddingValue = None + horizontal_spacing: OptionalNumber = None + min_leading_width: OptionalNumber = None + title_text_style: OptionalTextStyle = None + subtitle_text_style: OptionalTextStyle = None + leading_and_trailing_text_style: OptionalTextStyle = None + mouse_cursor: Optional[ControlStateValue[MouseCursor]] = None + min_tile_height: OptionalNumber = None + + +@dataclass +class TooltipTheme: + height: OptionalNumber = None + text_style: OptionalTextStyle = None + enable_feedback: OptionalBool = None + exclude_from_semantics: OptionalBool = None + prefer_below: OptionalBool = None + vertical_offset: OptionalNumber = None + padding: OptionalPaddingValue = None + wait_duration: OptionalDurationValue = None + exit_duration: OptionalDurationValue = None + show_duration: OptionalDurationValue = None + margin: OptionalMarginValue = None + trigger_mode: Optional[TooltipTriggerMode] = None + decoration: Optional[BoxDecoration] = None + text_align: Optional[TextAlign] = None + + +@dataclass +class ExpansionTileTheme: + bgcolor: OptionalColorValue = None + icon_color: OptionalColorValue = None + text_color: OptionalColorValue = None + collapsed_bgcolor: OptionalColorValue = None + collapsed_icon_color: OptionalColorValue = None + clip_behavior: Optional[ClipBehavior] = None + collapsed_text_color: OptionalColorValue = None + tile_padding: OptionalPaddingValue = None + expanded_alignment: Optional[Alignment] = None + controls_padding: OptionalPaddingValue = None + + +@dataclass +class SliderTheme: + active_track_color: OptionalColorValue = None + inactive_track_color: OptionalColorValue = None + thumb_color: OptionalColorValue = None + overlay_color: OptionalColorValue = None + value_indicator_color: OptionalColorValue = None + disabled_thumb_color: OptionalColorValue = None + value_indicator_text_style: OptionalTextStyle = None + mouse_cursor: Optional[ControlStateValue[MouseCursor]] = None + active_tick_mark_color: OptionalColorValue = None + disabled_active_tick_mark_color: OptionalColorValue = None + disabled_active_track_color: OptionalColorValue = None + disabled_inactive_tick_mark_color: OptionalColorValue = None + disabled_inactive_track_color: OptionalColorValue = None + disabled_secondary_active_track_color: OptionalColorValue = None + inactive_tick_mark_color: OptionalColorValue = None + overlapping_shape_stroke_color: OptionalColorValue = None + min_thumb_separation: OptionalNumber = None + secondary_active_track_color: OptionalColorValue = None + track_height: OptionalNumber = None + value_indicator_stroke_color: OptionalColorValue = None + interaction: Optional[SliderInteraction] = None + padding: OptionalPaddingValue = None + track_gap: OptionalNumber = None + thumb_size: Optional[ControlStateValue[Size]] = None + year_2023: OptionalBool = None + + +@dataclass +class ProgressIndicatorTheme: + color: OptionalColorValue = None + circular_track_color: OptionalColorValue = None + linear_track_color: OptionalColorValue = None + refresh_bgcolor: OptionalColorValue = None + linear_min_height: OptionalNumber = None + border_radius: OptionalBorderRadiusValue = None + track_gap: OptionalNumber = None + circular_track_padding: OptionalPaddingValue = None + size_constraints: Optional[BoxConstraints] = None + stop_indicator_color: OptionalColorValue = None + stop_indicator_radius: OptionalNumber = None + stroke_align: OptionalNumber = None + stroke_cap: Optional[StrokeCap] = None + stroke_width: OptionalNumber = None + year_2023: OptionalBool = None + + +@dataclass +class PopupMenuTheme: + color: OptionalColorValue = None + surface_tint_color: OptionalColorValue = None + shadow_color: OptionalColorValue = None + icon_color: OptionalColorValue = None + text_style: OptionalTextStyle = None + label_text_style: OptionalTextStyle = None + enable_feedback: OptionalBool = None + elevation: OptionalNumber = None + icon_size: OptionalNumber = None + shape: Optional[OutlinedBorder] = None + menu_position: Optional[PopupMenuPosition] = None + mouse_cursor: Optional[ControlStateValue[MouseCursor]] = None + menu_padding: OptionalPaddingValue = None + + +@dataclass +class SearchBarTheme: + bgcolor: OptionalColorValue = None + text_capitalization: Optional[TextCapitalization] = None + shadow_color: Optional[ControlStateValue[ColorValue]] = None + surface_tint_color: Optional[ControlStateValue[ColorValue]] = None + overlay_color: Optional[ControlStateValue[ColorValue]] = None + elevation: Optional[ControlStateValue[OptionalNumber]] = None + text_style: Optional[ControlStateValue[TextStyle]] = None + hint_style: Optional[ControlStateValue[TextStyle]] = None + shape: Optional[ControlStateValue[OutlinedBorder]] = None + padding: Optional[ControlStateValue[PaddingValue]] = None + size_constraints: Optional[BoxConstraints] = None + border_side: Optional[ControlStateValue[BorderSide]] = None + + +@dataclass +class SearchViewTheme: + bgcolor: OptionalColorValue = None + surface_tint_color: OptionalColorValue = None + divider_color: OptionalColorValue = None + elevation: OptionalNumber = None + header_hint_text_style: OptionalTextStyle = None + header_text_style: OptionalTextStyle = None + shape: Optional[OutlinedBorder] = None + border_side: OptionalBorderSide = None + size_constraints: Optional[BoxConstraints] = None + header_height: OptionalNumber = None + padding: OptionalPaddingValue = None + bar_padding: OptionalPaddingValue = None + shrink_wrap: OptionalBool = None + + +@dataclass +class NavigationDrawerTheme: + bgcolor: OptionalColorValue = None + shadow_color: OptionalColorValue = None + surface_tint_color: OptionalColorValue = None + indicator_color: OptionalColorValue = None + elevation: OptionalNumber = None + tile_height: OptionalNumber = None + label_text_style: Optional[ControlStateValue[TextStyle]] = None + indicator_shape: Optional[OutlinedBorder] = None + indicator_size: Optional[Size] = None + + +@dataclass +class NavigationBarTheme: + bgcolor: OptionalColorValue = None + shadow_color: OptionalColorValue = None + surface_tint_color: OptionalColorValue = None + indicator_color: OptionalColorValue = None + overlay_color: Optional[ControlStateValue[ColorValue]] = None + elevation: OptionalNumber = None + height: OptionalNumber = None + label_text_style: Optional[ControlStateValue[TextStyle]] = None + indicator_shape: Optional[OutlinedBorder] = None + label_behavior: Optional[NavigationBarLabelBehavior] = None + label_padding: OptionalPaddingValue = None + + +@dataclass +class SegmentedButtonTheme: + selected_icon: Optional[IconValue] = None + style: Optional[ButtonStyle] = None + + +@dataclass +class IconTheme: + color: OptionalColorValue = None + apply_text_scaling: OptionalBool = None + fill: OptionalNumber = None + opacity: OptionalNumber = None + size: OptionalNumber = None + optical_size: OptionalNumber = None + grade: OptionalNumber = None + weight: OptionalNumber = None + shadows: Optional[list[BoxShadow]] = None + + +@dataclass +class DataTableTheme: + checkbox_horizontal_margin: OptionalNumber = None + column_spacing: OptionalNumber = None + data_row_max_height: OptionalNumber = None + data_row_min_height: OptionalNumber = None + data_row_color: Optional[ControlStateValue[ColorValue]] = None + data_text_style: OptionalTextStyle = None + divider_thickness: OptionalNumber = None + horizontal_margin: OptionalNumber = None + heading_text_style: OptionalTextStyle = None + heading_row_color: Optional[ControlStateValue[ColorValue]] = None + heading_row_height: OptionalNumber = None + data_row_cursor: Optional[ControlStateValue[MouseCursor]] = None + decoration: Optional[BoxDecoration] = None + heading_row_alignment: Optional[MainAxisAlignment] = None + heading_cell_cursor: Optional[ControlStateValue[MouseCursor]] = None + + +@dataclass +class Theme: + color_scheme_seed: OptionalColorValue = None + primary_swatch: OptionalColorValue = None + font_family: Optional[str] = None + use_material3: OptionalBool = None + appbar_theme: Optional[AppBarTheme] = None + badge_theme: Optional[BadgeTheme] = None + banner_theme: Optional[BannerTheme] = None + bottom_appbar_theme: Optional[BottomAppBarTheme] = None + bottom_sheet_theme: Optional[BottomSheetTheme] = None + card_theme: Optional[CardTheme] = None + checkbox_theme: Optional[CheckboxTheme] = None + chip_theme: Optional[ChipTheme] = None + color_scheme: Optional[ColorScheme] = None + data_table_theme: Optional[DataTableTheme] = None + date_picker_theme: Optional[DatePickerTheme] = None + dialog_theme: Optional[DialogTheme] = None + divider_theme: Optional[DividerTheme] = None + # dropdown_menu_theme: Optional[DropdownMenuTheme] = None + elevated_button_theme: Optional[ElevatedButtonTheme] = None + outlined_button_theme: Optional[OutlinedButtonTheme] = None + text_button_theme: Optional[TextButtonTheme] = None + filled_button_theme: Optional[FilledButtonTheme] = None + icon_button_theme: Optional[IconButtonTheme] = None + expansion_tile_theme: Optional[ExpansionTileTheme] = None + floating_action_button_theme: Optional[FloatingActionButtonTheme] = None + icon_theme: Optional[IconTheme] = None + list_tile_theme: Optional[ListTileTheme] = None + navigation_bar_theme: Optional[NavigationBarTheme] = None + navigation_drawer_theme: Optional[NavigationDrawerTheme] = None + navigation_rail_theme: Optional[NavigationRailTheme] = None + page_transitions: PageTransitionsTheme = field(default_factory=PageTransitionsTheme) + popup_menu_theme: Optional[PopupMenuTheme] = None + splash_color: OptionalColorValue = None + highlight_color: OptionalColorValue = None + hover_color: OptionalColorValue = None + focus_color: OptionalColorValue = None + unselected_control_color: OptionalColorValue = None + disabled_color: OptionalColorValue = None + canvas_color: OptionalColorValue = None + scaffold_bgcolor: OptionalColorValue = None + card_color: OptionalColorValue = None + divider_color: OptionalColorValue = None + indicator_color: OptionalColorValue = None + hint_color: OptionalColorValue = None + shadow_color: OptionalColorValue = None + secondary_header_color: OptionalColorValue = None + primary_color: OptionalColorValue = None + primary_color_dark: OptionalColorValue = None + primary_color_light: OptionalColorValue = None + primary_text_theme: Optional[TextTheme] = None + progress_indicator_theme: Optional[ProgressIndicatorTheme] = None + radio_theme: Optional[RadioTheme] = None + scrollbar_theme: Optional[ScrollbarTheme] = None + search_bar_theme: Optional[SearchBarTheme] = None + search_view_theme: Optional[SearchViewTheme] = None + segmented_button_theme: Optional[SegmentedButtonTheme] = None + slider_theme: Optional[SliderTheme] = None + snackbar_theme: Optional[SnackBarTheme] = None + switch_theme: Optional[SwitchTheme] = None + system_overlay_style: SystemOverlayStyle = field(default_factory=SystemOverlayStyle) + tabs_theme: Optional[TabsTheme] = None + text_theme: Optional[TextTheme] = None + time_picker_theme: Optional[TimePickerTheme] = None + tooltip_theme: Optional[TooltipTheme] = None + visual_density: Optional[VisualDensity] = None diff --git a/sdk/python/packages/flet/src/flet/controls/transform.py b/sdk/python/packages/flet/src/flet/controls/transform.py new file mode 100644 index 000000000..f131e6daf --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/transform.py @@ -0,0 +1,50 @@ +from dataclasses import dataclass +from typing import Optional, Tuple, Union + +from flet.controls.alignment import OptionalAlignment +from flet.controls.types import Number, OptionalNumber + +__all__ = [ + "Scale", + "Rotate", + "Offset", + "ScaleValue", + "RotateValue", + "OffsetValue", + "OptionalScaleValue", + "OptionalRotateValue", + "OptionalOffsetValue", +] + + +@dataclass +class Scale: + scale: OptionalNumber = None + scale_x: OptionalNumber = None + scale_y: OptionalNumber = None + alignment: OptionalAlignment = None + + +@dataclass +class Rotate: + angle: Number + alignment: OptionalAlignment = None + + +@dataclass +class Offset: + x: Number + y: Number + + @classmethod + def zero(cls): + return Offset(0, 0) + + +# typing +RotateValue = Union[Number, Rotate] +OptionalRotateValue = Optional[RotateValue] +ScaleValue = Union[Number, Scale] +OptionalScaleValue = Optional[ScaleValue] +OffsetValue = Union[Offset, Tuple[Number, Number]] +OptionalOffsetValue = Optional[OffsetValue] diff --git a/sdk/python/packages/flet/src/flet/core/types.py b/sdk/python/packages/flet/src/flet/controls/types.py similarity index 72% rename from sdk/python/packages/flet/src/flet/core/types.py rename to sdk/python/packages/flet/src/flet/controls/types.py index b85d1a148..f4c156c5c 100644 --- a/sdk/python/packages/flet/src/flet/core/types.py +++ b/sdk/python/packages/flet/src/flet/controls/types.py @@ -1,18 +1,21 @@ from dataclasses import dataclass -from datetime import date, datetime from enum import Enum -from typing import Any, Callable, Dict, List, Optional, Protocol, Tuple, TypeVar, Union - -from flet.core.border_radius import BorderRadius -from flet.core.colors import Colors -from flet.core.control_event import ControlEvent -from flet.core.cupertino_colors import CupertinoColors -from flet.core.cupertino_icons import CupertinoIcons -from flet.core.event import Event -from flet.core.icons import Icons -from flet.core.margin import Margin -from flet.core.padding import Padding -from flet.core.transform import Offset, Rotate, Scale +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Optional, + Protocol, + Union, +) + +from flet.controls.colors import Colors +from flet.controls.cupertino.cupertino_colors import CupertinoColors +from flet.controls.cupertino.cupertino_icons import CupertinoIcons +from flet.controls.material.icons import Icons + +if TYPE_CHECKING: + from flet.controls.control import Control WEB_BROWSER = "web_browser" FLET_APP = "flet_app" @@ -27,28 +30,15 @@ class AppView(Enum): FLET_APP_HIDDEN = "flet_app_hidden" -class WindowEventType(Enum): - CLOSE = "close" - FOCUS = "focus" - BLUR = "blur" - HIDE = "hide" - SHOW = "show" - MAXIMIZE = "maximize" - UNMAXIMIZE = "unmaximize" - MINIMIZE = "minimize" - RESTORE = "restore" - RESIZE = "resize" - RESIZED = "resized" - MOVE = "move" - MOVED = "moved" - LEAVE_FULL_SCREEN = "leave-full-screen" - ENTER_FULL_SCREEN = "enter-full-screen" - - class WebRenderer(Enum): AUTO = "auto" - HTML = "html" CANVAS_KIT = "canvaskit" + SKWASM = "skwasm" + + +class RouteUrlStrategy(Enum): + PATH = "path" + HASH = "hash" class UrlTarget(Enum): @@ -58,32 +48,6 @@ class UrlTarget(Enum): TOP = "_top" -PaddingValue = Union[int, float, Padding] - -MarginValue = Union[int, float, Margin] - -BorderRadiusValue = Union[int, float, BorderRadius] - -RotateValue = Union[int, float, Rotate] - -ScaleValue = Union[int, float, Scale] - -OffsetValue = Union[Offset, Tuple[Union[float, int], Union[float, int]]] - - -@dataclass -class Duration: - microseconds: int = 0 - milliseconds: int = 0 - seconds: int = 0 - minutes: int = 0 - hours: int = 0 - days: int = 0 - - -DurationValue = Union[int, Duration] - - class FontWeight(Enum): NORMAL = "normal" BOLD = "bold" @@ -103,24 +67,28 @@ class NotchShape(Enum): CIRCULAR = "circular" +class ResponsiveRowBreakpoint(Enum): + """ + Breakpoints for responsive design. + """ + + XS = "xs" + SM = "sm" + MD = "md" + LG = "lg" + XL = "xl" + XXL = "xxl" + + Number = Union[int, float] -ResponsiveNumber = Union[Dict[str, Number], Number] +ResponsiveNumber = Union[dict[Union[str, ResponsiveRowBreakpoint], Number], Number] OptionalNumber = Optional[Number] -# str type alias +# literal type alias OptionalString = Optional[str] - - -class ControlState(Enum): - HOVERED = "hovered" - FOCUSED = "focused" - PRESSED = "pressed" - DRAGGED = "dragged" - SELECTED = "selected" - SCROLLED_UNDER = "scrolledUnder" - DISABLED = "disabled" - ERROR = "error" - DEFAULT = "default" +OptionalBool = Optional[bool] +OptionalInt = Optional[int] +OptionalFloat = Optional[float] class MainAxisAlignment(Enum): @@ -215,6 +183,7 @@ class ClipBehavior(Enum): HARD_EDGE = "hardEdge" +# todo: deprecate and remove in favor of BoxFit class ImageFit(Enum): NONE = "none" CONTAIN = "contain" @@ -240,6 +209,21 @@ class PagePlatform(Enum): WINDOWS = "windows" LINUX = "linux" + def is_apple(self) -> bool: + """Whether this PagePlatform instance is an Apple (iOS or macOS) platform.""" + return self in {PagePlatform.IOS, PagePlatform.MACOS} + + def is_mobile(self) -> bool: + """Whether this PagePlatform instance is a mobile (iOS or Android) platform.""" + return self in {PagePlatform.IOS, PagePlatform.ANDROID} + + def is_desktop(self) -> bool: + """ + Whether this PagePlatform instance is a desktop (macOS, Windows, Linux) + platform. + """ + return self in {PagePlatform.MACOS, PagePlatform.WINDOWS, PagePlatform.LINUX} + class ThemeMode(Enum): SYSTEM = "system" @@ -365,38 +349,22 @@ class Locale: @dataclass class LocaleConfiguration: - supported_locales: Optional[List[Locale]] = None + supported_locales: Optional[list[Locale]] = None current_locale: Optional[Locale] = None -# Events -ControlEventType = TypeVar("ControlEventType", bound=ControlEvent) -EventType = TypeVar("EventType", bound=Event) -OptionalEventCallable = Optional[Callable[[EventType], Any]] -OptionalControlEventCallable = Optional[Callable[[ControlEvent], Any]] - - -class OnFocusEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - self.primary: bool = bool(e.data) - - # Colors ColorEnums = (Colors, CupertinoColors) ColorValue = Union[str, Colors, CupertinoColors] +OptionalColorValue = Optional[ColorValue] # Icons IconEnums = (Icons, CupertinoIcons) IconValue = Union[str, Icons, CupertinoIcons] IconValueOrControl = Union[IconValue, "Control"] -# ControlState -T = TypeVar("T") -ControlStateValue = Union[None, T, Dict[ControlState, T]] - -# DateTime -DateTimeValue = Union[datetime, date] +# Content +StrOrControl = Union[str, "Control"] # Wrapper Wrapper = Callable[..., Any] @@ -404,5 +372,4 @@ def __init__(self, e: ControlEvent): # Protocols class SupportsStr(Protocol): - def __str__(self) -> str: - ... + def __str__(self) -> str: ... diff --git a/sdk/python/packages/flet/src/flet/controls/update_behavior.py b/sdk/python/packages/flet/src/flet/controls/update_behavior.py new file mode 100644 index 000000000..cfa51fd4b --- /dev/null +++ b/sdk/python/packages/flet/src/flet/controls/update_behavior.py @@ -0,0 +1,30 @@ +import contextvars + + +class UpdateBehavior: + _auto_update_enabled: bool = True + + @classmethod + def reset(cls): + """Copies parent state into current context.""" + current = _update_behavior_context_var.get() + new = UpdateBehavior() + new._auto_update_enabled = current._auto_update_enabled + _update_behavior_context_var.set(new) + + @classmethod + def enable_auto_update(cls): + _update_behavior_context_var.get()._auto_update_enabled = True + + @classmethod + def disable_auto_update(cls): + _update_behavior_context_var.get()._auto_update_enabled = False + + @classmethod + def auto_update_enabled(cls): + return _update_behavior_context_var.get()._auto_update_enabled + + +_update_behavior_context_var = contextvars.ContextVar( + "update_behavior", default=UpdateBehavior() +) diff --git a/sdk/python/packages/flet/src/flet/core/adaptive_control.py b/sdk/python/packages/flet/src/flet/core/adaptive_control.py deleted file mode 100644 index 0361f8d94..000000000 --- a/sdk/python/packages/flet/src/flet/core/adaptive_control.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Optional - -from flet.core.control import Control - - -class AdaptiveControl(Control): - def __init__(self, adaptive: Optional[bool] = None): - self.adaptive = adaptive - - # adaptive - @property - def adaptive(self) -> bool: - return self._get_attr("adaptive", data_type="bool", def_value=False) - - @adaptive.setter - def adaptive(self, value: Optional[bool]): - self._set_attr("adaptive", value) diff --git a/sdk/python/packages/flet/src/flet/core/ads/banner.py b/sdk/python/packages/flet/src/flet/core/ads/banner.py deleted file mode 100644 index 9a087b7a2..000000000 --- a/sdk/python/packages/flet/src/flet/core/ads/banner.py +++ /dev/null @@ -1,116 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.ads.base_ad import BaseAd -from flet.core.animation import AnimationValue -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.types import ( - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) -from flet.utils import deprecated - - -@deprecated( - reason="BannerAd control has been moved to a separate Python package: https://pypi.org/project/flet-ads. " - + "Read more about this change in Flet blog: https://flet.dev/blog/flet-v-0-26-release-announcement", - version="0.26.0", - delete_version="0.29.0", -) -class BannerAd(BaseAd): - """ - Displays a banner ad. - - ----- - - Online docs: https://flet.dev/docs/controls/bannerad - """ - - def __init__( - self, - unit_id: str, - on_load: OptionalControlEventCallable = None, - on_error: OptionalControlEventCallable = None, - on_open: OptionalControlEventCallable = None, - on_close: OptionalControlEventCallable = None, - on_impression: OptionalControlEventCallable = None, - on_click: OptionalControlEventCallable = None, - on_will_dismiss: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: AnimationValue = None, - animate_size: AnimationValue = None, - animate_position: AnimationValue = None, - animate_rotation: AnimationValue = None, - animate_scale: AnimationValue = None, - animate_offset: AnimationValue = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[str] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - BaseAd.__init__( - self, - unit_id=unit_id, - on_load=on_load, - on_error=on_error, - on_open=on_open, - on_close=on_close, - on_impression=on_impression, - on_click=on_click, - on_will_dismiss=on_will_dismiss, - # - # ConstrainedControl - # - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - visible=visible, - disabled=disabled, - data=data, - ) - - def _get_control_name(self): - return "banner_ad" diff --git a/sdk/python/packages/flet/src/flet/core/ads/base_ad.py b/sdk/python/packages/flet/src/flet/core/ads/base_ad.py deleted file mode 100644 index 68ece61c5..000000000 --- a/sdk/python/packages/flet/src/flet/core/ads/base_ad.py +++ /dev/null @@ -1,174 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.types import ( - OffsetValue, - OptionalControlEventCallable, - PagePlatform, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class BaseAd(ConstrainedControl): - def __init__( - self, - unit_id: str, - on_load: OptionalControlEventCallable = None, - on_error: OptionalControlEventCallable = None, - on_open: OptionalControlEventCallable = None, - on_close: OptionalControlEventCallable = None, - on_impression: OptionalControlEventCallable = None, - on_click: OptionalControlEventCallable = None, - on_will_dismiss: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: AnimationValue = None, - animate_size: AnimationValue = None, - animate_position: AnimationValue = None, - animate_rotation: AnimationValue = None, - animate_scale: AnimationValue = None, - animate_offset: AnimationValue = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[str] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - visible=visible, - disabled=disabled, - data=data, - ) - - self.on_load = on_load - self.on_error = on_error - self.on_open = on_open - self.on_close = on_close - self.on_impression = on_impression - self.on_click = on_click - self.on_will_dismiss = on_will_dismiss - self.unit_id = unit_id - - def before_update(self): - assert self.page.platform in [ - PagePlatform.ANDROID, - PagePlatform.IOS, - ], f"{self.__class__.__name__} is only supported on Mobile (Android and iOS). " - - @property - def unit_id(self) -> str: - return self._get_attr("unitId") - - @unit_id.setter - def unit_id(self, value: str): - self._set_attr("unitId", value) - - # on_load - @property - def on_load(self): - return self._get_event_handler("load") - - @on_load.setter - def on_load(self, handler): - self._add_event_handler("load", handler) - - # on_error - @property - def on_error(self): - return self._get_event_handler("error") - - @on_error.setter - def on_error(self, handler): - self._add_event_handler("error", handler) - - # on_open - @property - def on_open(self): - return self._get_event_handler("open") - - @on_open.setter - def on_open(self, handler): - self._add_event_handler("open", handler) - - # on_close - @property - def on_close(self): - return self._get_event_handler("close") - - @on_close.setter - def on_close(self, handler): - self._add_event_handler("close", handler) - - # on_click - @property - def on_click(self): - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler): - self._add_event_handler("click", handler) - - # on_impression - @property - def on_impression(self): - return self._get_event_handler("impression") - - @on_impression.setter - def on_impression(self, handler): - self._add_event_handler("impression", handler) - - # on_will_dismiss - @property - def on_will_dismiss(self): - return self._get_event_handler("willDismiss") - - @on_will_dismiss.setter - def on_will_dismiss(self, handler): - self._add_event_handler("willDismiss", handler) diff --git a/sdk/python/packages/flet/src/flet/core/ads/interstitial.py b/sdk/python/packages/flet/src/flet/core/ads/interstitial.py deleted file mode 100644 index 35f46dace..000000000 --- a/sdk/python/packages/flet/src/flet/core/ads/interstitial.py +++ /dev/null @@ -1,117 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.ads.base_ad import BaseAd -from flet.core.animation import AnimationValue -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.types import ( - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) -from flet.utils import deprecated - - -@deprecated( - reason="InterstitialAd control has been moved to a separate Python package: https://pypi.org/project/flet-ads. " - + "Read more about this change in Flet blog: https://flet.dev/blog/flet-v-0-26-release-announcement", - version="0.26.0", - delete_version="0.29.0", -) -class InterstitialAd(BaseAd): - """ - Displays a full screen interstitial ad. - - ----- - - Online docs: https://flet.dev/docs/controls/interstitialad - """ - - def __init__( - self, - unit_id: str, - on_load: OptionalControlEventCallable = None, - on_error: OptionalControlEventCallable = None, - on_open: OptionalControlEventCallable = None, - on_close: OptionalControlEventCallable = None, - on_impression: OptionalControlEventCallable = None, - on_click: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: AnimationValue = None, - animate_size: AnimationValue = None, - animate_position: AnimationValue = None, - animate_rotation: AnimationValue = None, - animate_scale: AnimationValue = None, - animate_offset: AnimationValue = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[str] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - BaseAd.__init__( - self, - unit_id=unit_id, - on_load=on_load, - on_error=on_error, - on_open=on_open, - on_close=on_close, - on_impression=on_impression, - on_click=on_click, - # - # ConstrainedControl - # - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - visible=visible, - disabled=disabled, - data=data, - ) - - def _get_control_name(self): - return "interstitial_ad" - - def show(self): - self.invoke_method("show") diff --git a/sdk/python/packages/flet/src/flet/core/ads/native.py b/sdk/python/packages/flet/src/flet/core/ads/native.py deleted file mode 100644 index 929b851a8..000000000 --- a/sdk/python/packages/flet/src/flet/core/ads/native.py +++ /dev/null @@ -1,170 +0,0 @@ -import dataclasses -from enum import Enum -from typing import Any, Optional, Union - -from flet.core.ads.base_ad import BaseAd -from flet.core.animation import AnimationValue -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.types import OffsetValue, ResponsiveNumber, RotateValue, ScaleValue - - -class NativeAdTemplateType(Enum): - SMALL = "small" - MEDIUM = "medium" - - -class NativeTemplateFontStyle(Enum): - NORMAL = "normal" - BOLD = "bold" - ITALIC = "italic" - MONOSPACE = "monospace" - - -@dataclasses.dataclass -class NativeAdTemplateTextStyle: - size: OptionalNumber = dataclasses.field(default=None) - text_color: Optional[str] = dataclasses.field(default=None) - bgcolor: Optional[str] = dataclasses.field(default=None) - style: Optional[NativeTemplateFontStyle] = dataclasses.field(default=None) - - -@dataclasses.dataclass -class NativeAdTemplateStyle: - template_type: Optional[NativeAdTemplateType] = dataclasses.field(default=None) - main_bgcolor: Optional[str] = dataclasses.field(default=None) - corner_radius: OptionalNumber = dataclasses.field(default=None) - call_to_action_text_style: Optional[NativeAdTemplateTextStyle] = dataclasses.field( - default=None - ) - primary_text_style: Optional[NativeAdTemplateTextStyle] = dataclasses.field( - default=None - ) - secondary_text_style: Optional[NativeAdTemplateTextStyle] = dataclasses.field( - default=None - ) - tertiary_text_style: Optional[NativeAdTemplateTextStyle] = dataclasses.field( - default=None - ) - - -class NativeAd(BaseAd): - """ - TBA - - ----- - - Online docs: https://flet.dev/docs/controls/nativead - """ - - def __init__( - self, - unit_id: str = None, - factory_id: str = None, - template_style: NativeAdTemplateStyle = None, - on_load=None, - on_error=None, - on_open=None, - on_close=None, - on_impression=None, - on_click=None, - on_will_dismiss=None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: AnimationValue = None, - animate_size: AnimationValue = None, - animate_position: AnimationValue = None, - animate_rotation: AnimationValue = None, - animate_scale: AnimationValue = None, - animate_offset: AnimationValue = None, - on_animation_end=None, - tooltip: Optional[str] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - BaseAd.__init__( - self, - unit_id=unit_id, - on_load=on_load, - on_error=on_error, - on_open=on_open, - on_close=on_close, - on_impression=on_impression, - on_click=on_click, - on_will_dismiss=on_will_dismiss, - # - # ConstrainedControl - # - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - visible=visible, - disabled=disabled, - data=data, - ) - - self.template_style = template_style - self.factory_id = factory_id - - def _get_control_name(self): - return "native_ad" - - def before_update(self): - super().before_update() - self._set_attr_json("templateStyle", self.__template_style) - - @property - def template_style(self): - return self.__template_style - - @template_style.setter - def template_style(self, value): - self.__template_style = value - - # factory_id - @property - def factory_id(self) -> Optional[str]: - return self._get_attr("factoryId") - - @factory_id.setter - def factory_id(self, value: Optional[str]): - self._set_attr("factoryId", value) diff --git a/sdk/python/packages/flet/src/flet/core/alert_dialog.py b/sdk/python/packages/flet/src/flet/core/alert_dialog.py deleted file mode 100644 index 24e8e29a9..000000000 --- a/sdk/python/packages/flet/src/flet/core/alert_dialog.py +++ /dev/null @@ -1,445 +0,0 @@ -from typing import Any, List, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.alignment import Alignment -from flet.core.buttons import OutlinedBorder -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.types import ( - ClipBehavior, - ColorEnums, - ColorValue, - MainAxisAlignment, - OptionalControlEventCallable, - PaddingValue, -) - - -class AlertDialog(AdaptiveControl): - """ - An alert dialog informs the user about situations that require acknowledgement. An alert dialog has an optional title and an optional list of actions. The title is displayed above the content and the actions are displayed below the content. - - Example: - ``` - import flet as ft - - - def main(page: ft.Page): - page.title = "AlertDialog examples" - page.horizontal_alignment = ft.CrossAxisAlignment.CENTER - - dlg = ft.AlertDialog( - title=ft.Text("Hi, this is a non-modal dialog!"), - on_dismiss=lambda e: page.add(ft.Text("Non-modal dialog dismissed")), - ) - - def handle_close(e): - page.close(dlg_modal) - page.add(ft.Text(f"Modal dialog closed with action: {e.control.text}")) - - dlg_modal = ft.AlertDialog( - modal=True, - title=ft.Text("Please confirm"), - content=ft.Text("Do you really want to delete all those files?"), - actions=[ - ft.TextButton("Yes", on_click=handle_close), - ft.TextButton("No", on_click=handle_close), - ], - actions_alignment=ft.MainAxisAlignment.END, - on_dismiss=lambda e: page.add( - ft.Text("Modal dialog dismissed"), - ), - ) - - page.add( - ft.ElevatedButton("Open dialog", on_click=lambda e: page.open(dlg)), - ft.ElevatedButton("Open modal dialog", on_click=lambda e: page.open(dlg_modal)), - ) - - - ft.app(target=main) - ``` - ----- - - Online docs: https://flet.dev/docs/controls/alertdialog - """ - - def __init__( - self, - modal: bool = False, - title: Optional[Union[Control, str]] = None, - content: Optional[Control] = None, - actions: Optional[List[Control]] = None, - bgcolor: Optional[ColorValue] = None, - elevation: OptionalNumber = None, - icon: Optional[Control] = None, - open: bool = False, - title_padding: Optional[PaddingValue] = None, - content_padding: Optional[PaddingValue] = None, - actions_padding: Optional[PaddingValue] = None, - actions_alignment: Optional[MainAxisAlignment] = None, - shape: Optional[OutlinedBorder] = None, - inset_padding: Optional[PaddingValue] = None, - icon_padding: Optional[PaddingValue] = None, - action_button_padding: Optional[PaddingValue] = None, - surface_tint_color: Optional[ColorValue] = None, - shadow_color: Optional[ColorValue] = None, - icon_color: Optional[ColorValue] = None, - scrollable: Optional[bool] = None, - actions_overflow_button_spacing: OptionalNumber = None, - alignment: Optional[Alignment] = None, - content_text_style: Optional[TextStyle] = None, - title_text_style: Optional[TextStyle] = None, - clip_behavior: Optional[ClipBehavior] = None, - semantics_label: Optional[str] = None, - barrier_color: Optional[ColorValue] = None, - on_dismiss: OptionalControlEventCallable = None, - # - # AdaptiveControl - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = None, - ): - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.open = open - self.bgcolor = bgcolor - self.elevation = elevation - self.icon = icon - self.modal = modal - self.title = title - self.title_padding = title_padding - self.content = content - self.content_padding = content_padding - self.actions = actions - self.actions_padding = actions_padding - self.actions_alignment = actions_alignment - self.shape = shape - self.inset_padding = inset_padding - self.semantics_label = semantics_label - self.on_dismiss = on_dismiss - self.clip_behavior = clip_behavior - self.action_button_padding = action_button_padding - self.shadow_color = shadow_color - self.surface_tint_color = surface_tint_color - self.icon_padding = icon_padding - self.icon_color = icon_color - self.scrollable = scrollable - self.actions_overflow_button_spacing = actions_overflow_button_spacing - self.alignment = alignment - self.content_text_style = content_text_style - self.title_text_style = title_text_style - self.barrier_color = barrier_color - - def _get_control_name(self): - return "alertdialog" - - def before_update(self): - super().before_update() - assert ( - self.__title or self.__content or self.__actions - ), "AlertDialog has nothing to display. Provide at minimum one of the following: title, content, actions" - self._set_attr_json("actionsPadding", self.__actions_padding) - self._set_attr_json("contentPadding", self.__content_padding) - self._set_attr_json("titlePadding", self.__title_padding) - self._set_attr_json("shape", self.__shape) - self._set_attr_json("insetPadding", self.__inset_padding) - self._set_attr_json("iconPadding", self.__icon_padding) - self._set_attr_json("actionButtonPadding", self.__action_button_padding) - self._set_attr_json("alignment", self.__alignment) - if isinstance(self.__content_text_style, TextStyle): - self._set_attr_json("contentTextStyle", self.__content_text_style) - if isinstance(self.__title_text_style, TextStyle): - self._set_attr_json("titleTextStyle", self.__title_text_style) - - def _get_children(self): - children = [] - if isinstance(self.__title, Control): - self.__title._set_attr_internal("n", "title") - children.append(self.__title) - if self.__icon: - self.__icon._set_attr_internal("n", "icon") - children.append(self.__icon) - if self.__content: - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - for action in self.__actions: - action._set_attr_internal("n", "action") - children.append(action) - return children - - # open - @property - def open(self) -> bool: - return self._get_attr("open", data_type="bool", def_value=False) - - @open.setter - def open(self, value: Optional[bool]): - self._set_attr("open", value) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # shadow_color - @property - def shadow_color(self) -> Optional[ColorValue]: - return self.__shadow_color - - @shadow_color.setter - def shadow_color(self, value: Optional[ColorValue]): - self.__shadow_color = value - self._set_enum_attr("shadowColor", value, ColorEnums) - - # barrier_color - @property - def barrier_color(self) -> Optional[ColorValue]: - return self.__barrier_color - - @barrier_color.setter - def barrier_color(self, value: Optional[ColorValue]): - self.__barrier_color = value - self._set_enum_attr("barrierColor", value, ColorEnums) - - # surface_tint_color - @property - def surface_tint_color(self) -> Optional[ColorValue]: - return self.__surface_tint_color - - @surface_tint_color.setter - def surface_tint_color(self, value: Optional[ColorValue]): - self.__surface_tint_color = value - self._set_enum_attr("surfaceTintColor", value, ColorEnums) - - # icon_color - @property - def icon_color(self) -> Optional[ColorValue]: - return self.__icon_color - - @icon_color.setter - def icon_color(self, value: Optional[ColorValue]): - self.__icon_color = value - self._set_enum_attr("iconColor", value, ColorEnums) - - # elevation - @property - def elevation(self) -> OptionalNumber: - return self._get_attr("elevation", data_type="float") - - @elevation.setter - def elevation(self, value: OptionalNumber): - self._set_attr("elevation", value) - - # actions_overflow_button_spacing - @property - def actions_overflow_button_spacing(self) -> OptionalNumber: - return self._get_attr("actionsOverflowButtonSpacing", data_type="float") - - @actions_overflow_button_spacing.setter - def actions_overflow_button_spacing(self, value: OptionalNumber): - self._set_attr("actionsOverflowButtonSpacing", value) - - # scrollable - @property - def scrollable(self) -> bool: - return self._get_attr("scrollable", data_type="bool", def_value=False) - - @scrollable.setter - def scrollable(self, value: Optional[bool]): - self._set_attr("scrollable", value) - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value - - # content_text_style - @property - def content_text_style(self) -> Optional[TextStyle]: - return self.__content_text_style - - @content_text_style.setter - def content_text_style(self, value: Optional[TextStyle]): - self.__content_text_style = value - - # title_text_style - @property - def title_text_style(self) -> Optional[TextStyle]: - return self.__title_text_style - - @title_text_style.setter - def title_text_style(self, value: Optional[TextStyle]): - self.__title_text_style = value - - # modal - @property - def modal(self) -> bool: - return self._get_attr("modal", data_type="bool", def_value=False) - - @modal.setter - def modal(self, value: Optional[bool]): - self._set_attr("modal", value) - - # title - @property - def title(self) -> Optional[Union[Control, str]]: - return self.__title - - @title.setter - def title(self, value: Optional[Union[Control, str]]): - self.__title = value - if not isinstance(value, Control): - self._set_attr("title", value) - - # icon - @property - def icon(self) -> Optional[Control]: - return self.__icon - - @icon.setter - def icon(self, value: Optional[Control]): - self.__icon = value - - # title_padding - @property - def title_padding(self) -> Optional[PaddingValue]: - return self.__title_padding - - @title_padding.setter - def title_padding(self, value: Optional[PaddingValue]): - self.__title_padding = value - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # content_padding - @property - def content_padding(self) -> Optional[PaddingValue]: - return self.__content_padding - - @content_padding.setter - def content_padding(self, value: Optional[PaddingValue]): - self.__content_padding = value - - # actions - @property - def actions(self) -> List[Control]: - return self.__actions - - @actions.setter - def actions(self, value: Optional[List[Control]]): - self.__actions = value if value is not None else [] - - # actions_padding - @property - def actions_padding(self) -> Optional[PaddingValue]: - return self.__actions_padding - - @actions_padding.setter - def actions_padding(self, value: Optional[PaddingValue]): - self.__actions_padding = value - - # actions_alignment - @property - def actions_alignment(self) -> Optional[MainAxisAlignment]: - return self.__actions_alignment - - @actions_alignment.setter - def actions_alignment(self, value: Optional[MainAxisAlignment]): - self.__actions_alignment = value - self._set_attr( - "actionsAlignment", - value.value if isinstance(value, MainAxisAlignment) else value, - ) - - # shape - @property - def shape(self) -> Optional[OutlinedBorder]: - return self.__shape - - @shape.setter - def shape(self, value: Optional[OutlinedBorder]): - self.__shape = value - - # inset_padding - @property - def inset_padding(self) -> Optional[PaddingValue]: - return self.__inset_padding - - @inset_padding.setter - def inset_padding(self, value: Optional[PaddingValue]): - self.__inset_padding = value - - # icon_padding - @property - def icon_padding(self) -> Optional[PaddingValue]: - return self.__icon_padding - - @icon_padding.setter - def icon_padding(self, value: Optional[PaddingValue]): - self.__icon_padding = value - - # action_button_padding - @property - def action_button_padding(self) -> Optional[PaddingValue]: - return self.__action_button_padding - - @action_button_padding.setter - def action_button_padding(self, value: Optional[PaddingValue]): - self.__action_button_padding = value - - # semantics_label - @property - def semantics_label(self) -> Optional[str]: - return self._get_attr("semanticsLabel") - - @semantics_label.setter - def semantics_label(self, value: Optional[str]): - self._set_attr("semanticsLabel", value) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # on_dismiss - @property - def on_dismiss(self) -> OptionalControlEventCallable: - return self._get_event_handler("dismiss") - - @on_dismiss.setter - def on_dismiss(self, handler: OptionalControlEventCallable): - self._add_event_handler("dismiss", handler) diff --git a/sdk/python/packages/flet/src/flet/core/alignment.py b/sdk/python/packages/flet/src/flet/core/alignment.py deleted file mode 100644 index a3f969ef4..000000000 --- a/sdk/python/packages/flet/src/flet/core/alignment.py +++ /dev/null @@ -1,25 +0,0 @@ -import dataclasses -from enum import Enum -from typing import Union - - -class Axis(Enum): - HORIZONTAL = "horizontal" - VERTICAL = "vertical" - - -@dataclasses.dataclass -class Alignment: - x: Union[float, int] - y: Union[float, int] - - -bottom_center = Alignment(0, 1) -bottom_left = Alignment(-1, 1) -bottom_right = Alignment(1, 1) -center = Alignment(0, 0) -center_left = Alignment(-1, 0) -center_right = Alignment(1, 0) -top_center = Alignment(0, -1) -top_left = Alignment(-1, -1) -top_right = Alignment(1, -1) diff --git a/sdk/python/packages/flet/src/flet/core/animated_switcher.py b/sdk/python/packages/flet/src/flet/core/animated_switcher.py deleted file mode 100644 index c45bdc051..000000000 --- a/sdk/python/packages/flet/src/flet/core/animated_switcher.py +++ /dev/null @@ -1,225 +0,0 @@ -from enum import Enum -from typing import Any, Optional, Union - -from flet.core.animation import AnimationCurve, AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal - -TransitionValueString = Literal["fade", "rotation", "scale"] - - -class AnimatedSwitcherTransition(Enum): - FADE = "fade" - ROTATION = "rotation" - SCALE = "scale" - - -class AnimatedSwitcher(ConstrainedControl): - """ - A control that by default does a cross-fade between a new control and the control previously set on the AnimatedSwitcher as a `content`. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - - c1 = ft.Container( - ft.Text("Hello!", style=ft.TextThemeStyle.HEADLINE_MEDIUM), - alignment=ft.alignment.center, - width=200, - height=200, - bgcolor=ft.colors.GREEN, - ) - c2 = ft.Container( - ft.Text("Bye!", size=50), - alignment=ft.alignment.center, - width=200, - height=200, - bgcolor=ft.colors.YELLOW, - ) - c = ft.AnimatedSwitcher( - content=c1, - transition=ft.AnimatedSwitcherTransition.SCALE, - duration=500, - reverse_duration=100, - switch_in_curve=ft.AnimationCurve.BOUNCE_OUT, - switch_out_curve=ft.AnimationCurve.BOUNCE_IN, - ) - - def animate(e): - c.content = c2 if c.content == c1 else c1 - c.update() - - page.add( - c, - ft.ElevatedButton("Animate!", on_click=animate), - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/animatedswitcher - """ - - def __init__( - self, - content: Control, - duration: Optional[int] = None, - reverse_duration: Optional[int] = None, - switch_in_curve: Optional[AnimationCurve] = None, - switch_out_curve: Optional[AnimationCurve] = None, - transition: Optional[AnimatedSwitcherTransition] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.content = content - self.duration = duration - self.reverse_duration = reverse_duration - self.switch_in_curve = switch_in_curve - self.switch_out_curve = switch_out_curve - self.transition = transition - - def _get_control_name(self): - return "animatedswitcher" - - def before_update(self): - super().before_update() - assert self.__content.visible, "content must be visible" - - def _get_children(self): - self.__content._set_attr_internal("n", "content") - return [self.__content] - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # duration - @property - def duration(self) -> int: - return self._get_attr("duration", data_type="int", def_value=1000) - - @duration.setter - def duration(self, value: Optional[int]): - self._set_attr("duration", value) - - # reverse_duration - @property - def reverse_duration(self) -> int: - return self._get_attr("reverseDuration", data_type="int", def_value=1000) - - @reverse_duration.setter - def reverse_duration(self, value: Optional[int]): - self._set_attr("reverseDuration", value) - - # switch_in_curve - @property - def switch_in_curve(self) -> Optional[AnimationCurve]: - return self.__switch_in_curve - - @switch_in_curve.setter - def switch_in_curve(self, value: Optional[AnimationCurve]): - self.__switch_in_curve = value - self._set_enum_attr("switchInCurve", value, AnimationCurve) - - # switch_out_curve - @property - def switch_out_curve(self) -> Optional[AnimationCurve]: - return self.__switch_out_curve - - @switch_out_curve.setter - def switch_out_curve(self, value: Optional[AnimationCurve]): - self.__switch_out_curve = value - self._set_enum_attr("switchOutCurve", value, AnimationCurve) - - # transition - @property - def transition(self) -> Optional[AnimatedSwitcherTransition]: - return self.__transition - - @transition.setter - def transition(self, value: Optional[AnimatedSwitcherTransition]): - self.__transition = value - self._set_enum_attr("transition", value, AnimatedSwitcherTransition) diff --git a/sdk/python/packages/flet/src/flet/core/app_bar.py b/sdk/python/packages/flet/src/flet/core/app_bar.py deleted file mode 100644 index 9853a16cd..000000000 --- a/sdk/python/packages/flet/src/flet/core/app_bar.py +++ /dev/null @@ -1,360 +0,0 @@ -from typing import Any, List, Optional - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.buttons import OutlinedBorder -from flet.core.control import Control -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.types import ClipBehavior, ColorEnums, ColorValue, OptionalNumber - - -class AppBar(AdaptiveControl): - """ - A material design app bar. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - def check_item_clicked(e): - e.control.checked = not e.control.checked - page.update() - - page.appbar = ft.AppBar( - leading=ft.Icon(ft.icons.PALETTE), - leading_width=40, - title=ft.Text("AppBar Example"), - center_title=False, - bgcolor=ft.colors.SURFACE_VARIANT, - actions=[ - ft.IconButton(ft.icons.WB_SUNNY_OUTLINED), - ft.IconButton(ft.icons.FILTER_3), - ft.PopupMenuButton( - items=[ - ft.PopupMenuItem(text="Item 1"), - ft.PopupMenuItem(), # divider - ft.PopupMenuItem( - text="Checked item", checked=False, on_click=check_item_clicked - ), - ] - ), - ], - ) - page.add(ft.Text("Body!")) - - ft.app(target=main) - - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/appbar - """ - - def __init__( - self, - leading: Optional[Control] = None, - leading_width: OptionalNumber = None, - automatically_imply_leading: Optional[bool] = None, - title: Optional[Control] = None, - center_title: Optional[bool] = None, - toolbar_height: OptionalNumber = None, - color: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, - elevation: OptionalNumber = None, - elevation_on_scroll: OptionalNumber = None, - shadow_color: Optional[ColorValue] = None, - surface_tint_color: Optional[ColorValue] = None, - clip_behavior: Optional[ClipBehavior] = None, - force_material_transparency: Optional[bool] = None, - is_secondary: Optional[bool] = None, - title_spacing: OptionalNumber = None, - exclude_header_semantics: Optional[bool] = None, - actions: Optional[List[Control]] = None, - toolbar_opacity: OptionalNumber = None, - title_text_style: Optional[TextStyle] = None, - toolbar_text_style: Optional[TextStyle] = None, - shape: Optional[OutlinedBorder] = None, - # - # AdaptiveControl - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - adaptive: Optional[bool] = None, - ): - Control.__init__( - self, ref=ref, visible=visible, disabled=disabled, data=data, rtl=rtl - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.leading = leading - self.leading_width = leading_width - self.automatically_imply_leading = automatically_imply_leading - self.title = title - self.center_title = center_title - self.toolbar_height = toolbar_height - self.color = color - self.bgcolor = bgcolor - self.elevation = elevation - self.actions = actions - self.elevation_on_scroll = elevation_on_scroll - self.shadow_color = shadow_color - self.surface_tint_color = surface_tint_color - self.clip_behavior = clip_behavior - self.force_material_transparency = force_material_transparency - self.is_secondary = is_secondary - self.title_spacing = title_spacing - self.exclude_header_semantics = exclude_header_semantics - self.toolbar_opacity = toolbar_opacity - self.title_text_style = title_text_style - self.toolbar_text_style = toolbar_text_style - self.shape = shape - - def _get_control_name(self): - return "appbar" - - def before_update(self): - super().before_update() - if isinstance(self.__title_text_style, TextStyle): - self._set_attr_json("titleTextStyle", self.__title_text_style) - if isinstance(self.__toolbar_text_style, TextStyle): - self._set_attr_json("toolbarTextStyle", self.__toolbar_text_style) - if isinstance(self.__shape, OutlinedBorder): - self._set_attr_json("shape", self.__shape) - - def _get_children(self): - children = [] - if self.__leading: - self.__leading._set_attr_internal("n", "leading") - children.append(self.__leading) - if self.__title: - self.__title._set_attr_internal("n", "title") - children.append(self.__title) - for action in self.__actions: - action._set_attr_internal("n", "action") - children.append(action) - return children - - # leading - @property - def leading(self) -> Optional[Control]: - return self.__leading - - @leading.setter - def leading(self, value: Optional[Control]): - """ - A Control to display before the toolbar's title. - - Typically the leading control is an Icon or an IconButton. - """ - self.__leading = value - - # leading_width - @property - def leading_width(self) -> OptionalNumber: - return self._get_attr("leadingWidth") - - @leading_width.setter - def leading_width(self, value: OptionalNumber): - self._set_attr("leadingWidth", value) - - # title_spacing - @property - def title_spacing(self) -> OptionalNumber: - return self._get_attr("titleSpacing", data_type="float") - - @title_spacing.setter - def title_spacing(self, value: OptionalNumber): - self._set_attr("titleSpacing", value) - - # toolbar_opacity - @property - def toolbar_opacity(self) -> float: - return self._get_attr("toolbarOpacity", data_type="float", def_value=1.0) - - @toolbar_opacity.setter - def toolbar_opacity(self, value: OptionalNumber): - assert value is None or ( - 0 <= value <= 1 - ), "toolbar_opacity is out of range (0-1)" - self._set_attr("toolbarOpacity", value) - - # shape - @property - def shape(self) -> Optional[OutlinedBorder]: - return self.__shape - - @shape.setter - def shape(self, value: Optional[OutlinedBorder]): - self.__shape = value - - # title_text_style - @property - def title_text_style(self) -> Optional[TextStyle]: - return self.__title_text_style - - @title_text_style.setter - def title_text_style(self, value: Optional[TextStyle]): - self.__title_text_style = value - - # toolbar_text_style - @property - def toolbar_text_style(self) -> Optional[TextStyle]: - return self.__toolbar_text_style - - @toolbar_text_style.setter - def toolbar_text_style(self, value: Optional[TextStyle]): - self.__toolbar_text_style = value - - # automatically_imply_leading - @property - def automatically_imply_leading(self) -> bool: - return self._get_attr( - "automaticallyImplyLeading", data_type="bool", def_value=True - ) - - @automatically_imply_leading.setter - def automatically_imply_leading(self, value: Optional[bool]): - self._set_attr("automaticallyImplyLeading", value) - - # title - @property - def title(self) -> Optional[Control]: - return self.__title - - @title.setter - def title(self, value: Optional[Control]): - self.__title = value - - # center_title - @property - def center_title(self) -> bool: - return self._get_attr("centerTitle", data_type="bool", def_value=False) - - @center_title.setter - def center_title(self, value: Optional[bool]): - self._set_attr("centerTitle", value) - - # toolbar_height - @property - def toolbar_height(self) -> OptionalNumber: - return self._get_attr("toolbarHeight") - - @toolbar_height.setter - def toolbar_height(self, value: OptionalNumber): - assert value is None or value >= 0, "toolbar_height cannot be negative" - self._set_attr("toolbarHeight", value) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # shadow_color - @property - def shadow_color(self) -> Optional[ColorValue]: - return self.__shadow_color - - @shadow_color.setter - def shadow_color(self, value: Optional[ColorValue]): - self.__shadow_color = value - self._set_enum_attr("shadowColor", value, ColorEnums) - - # surface_tint_color - @property - def surface_tint_color(self) -> Optional[ColorValue]: - return self.__surface_tint_color - - @surface_tint_color.setter - def surface_tint_color(self, value: Optional[ColorValue]): - self.__surface_tint_color = value - self._set_enum_attr("surfaceTintColor", value, ColorEnums) - - # is_secondary - @property - def is_secondary(self) -> bool: - return self._get_attr("isSecondary", data_type="bool", def_value=False) - - @is_secondary.setter - def is_secondary(self, value: Optional[bool]): - self._set_attr("isSecondary", value) - - # exclude_header_semantics - @property - def exclude_header_semantics(self) -> bool: - return self._get_attr( - "excludeHeaderSemantics", data_type="bool", def_value=False - ) - - @exclude_header_semantics.setter - def exclude_header_semantics(self, value: Optional[bool]): - self._set_attr("excludeHeaderSemantics", value) - - # force_material_transparency - @property - def force_material_transparency(self) -> bool: - return self._get_attr( - "forceMaterialTransparency", data_type="bool", def_value=False - ) - - @force_material_transparency.setter - def force_material_transparency(self, value: Optional[bool]): - self._set_attr("forceMaterialTransparency", value) - - # elevation - @property - def elevation(self) -> OptionalNumber: - return self._get_attr("elevation") - - @elevation.setter - def elevation(self, value: OptionalNumber): - assert value is None or value >= 0, "elevation cannot be negative" - self._set_attr("elevation", value) - - # elevation_on_scroll - @property - def elevation_on_scroll(self) -> OptionalNumber: - return self._get_attr("elevationOnScroll") - - @elevation_on_scroll.setter - def elevation_on_scroll(self, value: OptionalNumber): - assert value is None or value >= 0, "elevation_on_scroll cannot be negative" - self._set_attr("elevationOnScroll", value) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self._get_attr("clipBehavior") - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # actions - @property - def actions(self) -> Optional[List[Control]]: - return self.__actions - - @actions.setter - def actions(self, value: Optional[List[Control]]): - self.__actions = value if value is not None else [] diff --git a/sdk/python/packages/flet/src/flet/core/audio.py b/sdk/python/packages/flet/src/flet/core/audio.py deleted file mode 100644 index 0643a9a94..000000000 --- a/sdk/python/packages/flet/src/flet/core/audio.py +++ /dev/null @@ -1,298 +0,0 @@ -from enum import Enum -from typing import Any, Optional - -from flet.core.control import Control, OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.types import OptionalControlEventCallable, OptionalEventCallable -from flet.utils import deprecated - - -class ReleaseMode(Enum): - RELEASE = "release" - LOOP = "loop" - STOP = "stop" - - -class AudioState(Enum): - STOPPED = "stopped" - PLAYING = "playing" - PAUSED = "paused" - COMPLETED = "completed" - DISPOSED = "disposed" - - -class AudioStateChangeEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - self.state: AudioState = AudioState(e.data) - - -class AudioPositionChangeEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - self.position: int = int(e.data) - - -class AudioDurationChangeEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - self.duration: int = int(e.data) - - -@deprecated( - reason="Audio control has been moved to a separate Python package: https://pypi.org/project/flet-audio. " - + "Read more about this change in Flet blog: https://flet.dev/blog/flet-v-0-26-release-announcement", - version="0.26.0", - delete_version="0.29.0", -) -class Audio(Control): - """ - A control to simultaneously play multiple audio files. Works on macOS, Linux, Windows, iOS, Android and web. Based on audioplayers Flutter widget (https://pub.dev/packages/audioplayers). - - Audio control is non-visual and should be added to `page.overlay` list. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - audio1 = ft.Audio( - src="https://luan.xyz/files/audio/ambient_c_motion.mp3", autoplay=True - ) - page.overlay.append(audio1) - page.add( - ft.Text("This is an app with background audio."), - ft.ElevatedButton("Stop playing", on_click=lambda _: audio1.pause()), - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/audio - """ - - def __init__( - self, - src: Optional[str] = None, - src_base64: Optional[str] = None, - autoplay: Optional[bool] = None, - volume: OptionalNumber = None, - balance: OptionalNumber = None, - playback_rate: OptionalNumber = None, - release_mode: Optional[ReleaseMode] = None, - on_loaded: OptionalControlEventCallable = None, - on_duration_changed: OptionalEventCallable[AudioDurationChangeEvent] = None, - on_state_changed: OptionalEventCallable[AudioStateChangeEvent] = None, - on_position_changed: OptionalEventCallable[AudioPositionChangeEvent] = None, - on_seek_complete: OptionalControlEventCallable = None, - # - # Control - # - ref: Optional[Ref] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - data=data, - ) - - self.__on_state_changed = EventHandler(lambda e: AudioStateChangeEvent(e)) - self._add_event_handler("state_changed", self.__on_state_changed.get_handler()) - - self.__on_position_changed = EventHandler(lambda e: AudioPositionChangeEvent(e)) - self._add_event_handler( - "position_changed", self.__on_position_changed.get_handler() - ) - - self.__on_duration_changed = EventHandler(lambda e: AudioDurationChangeEvent(e)) - self._add_event_handler( - "duration_changed", self.__on_duration_changed.get_handler() - ) - - self.src = src - self.src_base64 = src_base64 - self.autoplay = autoplay - self.volume = volume - self.balance = balance - self.playback_rate = playback_rate - self.release_mode = release_mode - self.on_loaded = on_loaded - self.on_duration_changed = on_duration_changed - self.on_state_changed = on_state_changed - self.on_position_changed = on_position_changed - self.on_seek_complete = on_seek_complete - - def _get_control_name(self): - return "audio" - - def play(self): - self.invoke_method("play") - - def pause(self): - self.invoke_method("pause") - - def resume(self): - self.invoke_method("resume") - - def release(self): - self.invoke_method("release") - - def seek(self, position_milliseconds: int): - self.invoke_method("seek", {"position": str(position_milliseconds)}) - - def get_duration(self, wait_timeout: Optional[float] = 5) -> Optional[int]: - sr = self.invoke_method( - "get_duration", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return int(sr) if sr else None - - async def get_duration_async( - self, wait_timeout: Optional[float] = 5 - ) -> Optional[int]: - sr = await self.invoke_method_async( - "get_duration", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return int(sr) if sr else None - - def get_current_position(self, wait_timeout: Optional[float] = 5) -> Optional[int]: - sr = self.invoke_method( - "get_current_position", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return int(sr) if sr else None - - async def get_current_position_async( - self, wait_timeout: Optional[float] = 5 - ) -> Optional[int]: - sr = await self.invoke_method_async( - "get_current_position", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return int(sr) if sr else None - - # src - @property - def src(self): - return self._get_attr("src") - - @src.setter - def src(self, value): - self._set_attr("src", value) - - # src_base64 - @property - def src_base64(self): - return self._get_attr("srcBase64") - - @src_base64.setter - def src_base64(self, value): - self._set_attr("srcBase64", value) - - # autoplay - @property - def autoplay(self) -> bool: - return self._get_attr("autoplay", data_type="bool", def_value=False) - - @autoplay.setter - def autoplay(self, value: Optional[bool]): - self._set_attr("autoplay", value) - - # volume - @property - def volume(self) -> OptionalNumber: - return self._get_attr("volume") - - @volume.setter - def volume(self, value: OptionalNumber): - if value is None or (0 <= value <= 1): - self._set_attr("volume", value) - - # balance - @property - def balance(self) -> OptionalNumber: - return self._get_attr("balance") - - @balance.setter - def balance(self, value: OptionalNumber): - if value is None or (-1 <= value <= 1): - self._set_attr("balance", value) - - # playback_rate - @property - def playback_rate(self) -> OptionalNumber: - return self._get_attr("playbackRate") - - @playback_rate.setter - def playback_rate(self, value: OptionalNumber): - if value is None or (0 <= value <= 2): - self._set_attr("playbackRate", value) - - # release_mode - @property - def release_mode(self): - return self._get_attr("releaseMode") - - @release_mode.setter - def release_mode(self, value: Optional[ReleaseMode]): - self._set_enum_attr("releaseMode", value, ReleaseMode) - - # on_loaded - @property - def on_loaded(self): - return self._get_event_handler("loaded") - - @on_loaded.setter - def on_loaded(self, handler: OptionalControlEventCallable): - self._add_event_handler("loaded", handler) - - # on_duration_changed - @property - def on_duration_changed(self): - return self.__on_duration_changed.handler - - @on_duration_changed.setter - def on_duration_changed( - self, handler: OptionalEventCallable[AudioDurationChangeEvent] - ): - self.__on_duration_changed.handler = handler - - # on_state_changed - @property - def on_state_changed(self): - return self.__on_state_changed.handler - - @on_state_changed.setter - def on_state_changed(self, handler: OptionalEventCallable[AudioStateChangeEvent]): - self.__on_state_changed.handler = handler - - # on_position_changed - @property - def on_position_changed(self): - return self.__on_position_changed.handler - - @on_position_changed.setter - def on_position_changed( - self, handler: OptionalEventCallable[AudioPositionChangeEvent] - ): - self.__on_position_changed.handler = handler - self._set_attr("onPositionChanged", True if handler is not None else None) - - # on_seek_complete - @property - def on_seek_complete(self): - return self._get_event_handler("seek_complete") - - @on_seek_complete.setter - def on_seek_complete(self, handler: OptionalControlEventCallable): - self._add_event_handler("seek_complete", handler) diff --git a/sdk/python/packages/flet/src/flet/core/audio_recorder.py b/sdk/python/packages/flet/src/flet/core/audio_recorder.py deleted file mode 100644 index 7fa256a60..000000000 --- a/sdk/python/packages/flet/src/flet/core/audio_recorder.py +++ /dev/null @@ -1,314 +0,0 @@ -import json -from enum import Enum -from typing import Any, Optional - -from flet.core.control import Control, OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.types import OptionalEventCallable -from flet.utils import deprecated - - -class AudioRecorderState(Enum): - STOPPED = "stopped" - RECORDING = "recording" - PAUSED = "paused" - - -class AudioRecorderStateChangeEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - self.state: AudioRecorderState = AudioRecorderState(e.data) - - -class AudioEncoder(Enum): - AACLC = "aacLc" - AACELD = "aacEld" - AACHE = "aacHe" - AMRNB = "amrNb" - AMRWB = "amrWb" - OPUS = "opus" - FLAC = "flac" - WAV = "wav" - PCM16BITS = "pcm16bits" - - -@deprecated( - reason="AudioRecorder control has been moved to a separate Python package: https://pypi.org/project/flet-audio-recorder. " - + "Read more about this change in Flet blog: https://flet.dev/blog/flet-v-0-26-release-announcement", - version="0.26.0", - delete_version="0.29.0", -) -class AudioRecorder(Control): - """ - A control that allows you to record audio from your device. - - ----- - - Online docs: https://flet.dev/docs/controls/audiorecorder - """ - - def __init__( - self, - audio_encoder: Optional[AudioEncoder] = None, - suppress_noise: Optional[bool] = None, - cancel_echo: Optional[bool] = None, - auto_gain: Optional[bool] = None, - channels_num: OptionalNumber = None, - sample_rate: OptionalNumber = None, - bit_rate: OptionalNumber = None, - on_state_changed: OptionalEventCallable[AudioRecorderStateChangeEvent] = None, - # - # Control - # - ref: Optional[Ref] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - data=data, - ) - self.__on_state_changed = EventHandler( - lambda e: AudioRecorderStateChangeEvent(e) - ) - self._add_event_handler("state_changed", self.__on_state_changed.get_handler()) - - self.audio_encoder = audio_encoder - self.suppress_noise = suppress_noise - self.cancel_echo = cancel_echo - self.auto_gain = auto_gain - self.channels_num = channels_num - self.sample_rate = sample_rate - self.bit_rate = bit_rate - self.on_state_changed = on_state_changed - - def _get_control_name(self): - return "audiorecorder" - - def start_recording( - self, output_path: str = None, wait_timeout: Optional[float] = 10 - ) -> bool: - assert ( - self.page.web or output_path - ), "output_path must be provided when not on web" - started = self.invoke_method( - "start_recording", - {"outputPath": output_path}, - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return started == "true" - - async def start_recording_async( - self, output_path: str = None, wait_timeout: Optional[float] = 10 - ) -> bool: - assert ( - self.page.web or output_path - ), "output_path must be provided when not on web" - started = await self.invoke_method_async( - "start_recording", - {"outputPath": output_path}, - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return started == "true" - - def is_recording(self, wait_timeout: Optional[float] = 5) -> bool: - recording = self.invoke_method( - "is_recording", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return recording == "true" - - async def is_recording_async(self, wait_timeout: Optional[float] = 5) -> bool: - recording = await self.invoke_method_async( - "is_recording", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return recording == "true" - - def stop_recording(self, wait_timeout: Optional[float] = 5) -> Optional[str]: - return self.invoke_method( - "stop_recording", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - - async def stop_recording_async( - self, wait_timeout: Optional[float] = 10 - ) -> Optional[str]: - return await self.invoke_method_async( - "stop_recording", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - - def cancel_recording(self, wait_timeout: Optional[float] = 5) -> None: - self.invoke_method( - "cancel_recording", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - - def resume_recording(self): - self.invoke_method("resume_recording") - - def pause_recording(self): - self.invoke_method("pause_recording") - - def is_paused(self, wait_timeout: Optional[float] = 5) -> bool: - paused = self.invoke_method( - "is_paused", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return paused == "true" - - async def is_paused_async(self, wait_timeout: Optional[float] = 5) -> bool: - supported = await self.invoke_method_async( - "is_paused", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return supported == "true" - - def is_supported_encoder( - self, encoder: AudioEncoder, wait_timeout: Optional[float] = 5 - ) -> bool: - supported = self.invoke_method( - "is_supported_encoder", - { - "encoder": ( - encoder.value if isinstance(encoder, AudioEncoder) else encoder - ) - }, - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return supported == "true" - - async def is_supported_encoder_async( - self, encoder: AudioEncoder, wait_timeout: Optional[float] = 5 - ) -> bool: - supported = await self.invoke_method_async( - "is_supported_encoder", - { - "encoder": ( - encoder.value if isinstance(encoder, AudioEncoder) else encoder - ) - }, - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return supported == "true" - - def get_input_devices(self, wait_timeout: Optional[float] = 5) -> dict: - devices = self.invoke_method( - "get_input_devices", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return json.loads(devices) - - async def get_input_devices_async(self, wait_timeout: Optional[float] = 5) -> dict: - devices = await self.invoke_method_async( - "get_input_devices", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return json.loads(devices) - - def has_permission(self, wait_timeout: Optional[float] = 10) -> bool: - p = self.invoke_method( - "has_permission", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return p == "true" - - async def has_permission_async(self, wait_timeout: Optional[float] = 10) -> bool: - p = await self.invoke_method_async( - "has_permission", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return p == "true" - - # audio_encoder - @property - def audio_encoder(self): - return self._get_attr("audioEncoder") - - @audio_encoder.setter - def audio_encoder(self, value: Optional[AudioEncoder]): - self._set_enum_attr("audioEncoder", value, AudioEncoder) - - # suppress_noise - @property - def suppress_noise(self) -> bool: - return self._get_attr("suppressNoise", data_type="bool", def_value=False) - - @suppress_noise.setter - def suppress_noise(self, value: Optional[bool]): - self._set_attr("suppressNoise", value) - - # cancel_echo - @property - def cancel_echo(self) -> bool: - return self._get_attr("cancelEcho", data_type="bool", def_value=False) - - @cancel_echo.setter - def cancel_echo(self, value: Optional[bool]): - self._set_attr("cancelEcho", value) - - # auto_gain - @property - def auto_gain(self) -> bool: - return self._get_attr("autoGain", data_type="bool", def_value=False) - - @auto_gain.setter - def auto_gain(self, value: Optional[bool]): - self._set_attr("autoGain", value) - - # bit_rate - @property - def bit_rate(self) -> OptionalNumber: - return self._get_attr("bitRate") - - @bit_rate.setter - def bit_rate(self, value: OptionalNumber): - self._set_attr("bitRate", value) - - # sample_rate - @property - def sample_rate(self) -> OptionalNumber: - return self._get_attr("sampleRate") - - @sample_rate.setter - def sample_rate(self, value: OptionalNumber): - self._set_attr("sampleRate", value) - - # channels_num - @property - def channels_num(self) -> OptionalNumber: - return self._get_attr("channels") - - @channels_num.setter - def channels_num(self, value: OptionalNumber): - if value is None or value in (1, 2): - self._set_attr("channels", value) - - # on_state_changed - @property - def on_state_changed(self): - return self.__on_state_changed.handler - - @on_state_changed.setter - def on_state_changed( - self, handler: OptionalEventCallable[AudioRecorderStateChangeEvent] - ): - self.__on_state_changed.handler = handler diff --git a/sdk/python/packages/flet/src/flet/core/auto_complete.py b/sdk/python/packages/flet/src/flet/core/auto_complete.py deleted file mode 100644 index 24a67a6e5..000000000 --- a/sdk/python/packages/flet/src/flet/core/auto_complete.py +++ /dev/null @@ -1,104 +0,0 @@ -import json -from dataclasses import dataclass, field -from typing import Any, List, Optional - -from flet.core.control import Control, OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.types import OptionalEventCallable - - -@dataclass -class AutoCompleteSuggestion: - key: str = field(default=None) - value: str = field(default=None) - - -class AutoComplete(Control): - """ - Helps the user make a selection by entering some text and choosing from among a list of displayed options. - - ----- - - Online docs: https://flet.dev/docs/controls/autocomplete - """ - - def __init__( - self, - suggestions: Optional[List[AutoCompleteSuggestion]] = None, - suggestions_max_height: OptionalNumber = None, - on_select: OptionalEventCallable["AutoCompleteSelectEvent"] = None, - # - # Control - # - ref: Optional[Ref] = None, - opacity: OptionalNumber = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - opacity=opacity, - visible=visible, - data=data, - ) - - self.__on_select = EventHandler(lambda e: AutoCompleteSelectEvent(e)) - self._add_event_handler("select", self.__on_select.get_handler()) - - self.suggestions = suggestions - self.suggestions_max_height = suggestions_max_height - self.on_select = on_select - - def _get_control_name(self): - return "autocomplete" - - def before_update(self): - self._set_attr_json("suggestions", self.__suggestions) - - # selected_index - @property - def selected_index(self) -> Optional[int]: - return self._get_attr("selectedIndex", data_type="int") - - # suggestions_max_height - @property - def suggestions_max_height(self) -> float: - return self._get_attr( - "suggestionsMaxHeight", data_type="float", def_value=200.0 - ) - - @suggestions_max_height.setter - def suggestions_max_height(self, value: OptionalNumber): - assert value is None or value >= 0, "suggestions_max_height cannot be negative" - self._set_attr("suggestionsMaxHeight", value) - - # suggestions - @property - def suggestions(self) -> Optional[List[AutoCompleteSuggestion]]: - return self.__suggestions - - @suggestions.setter - def suggestions(self, value: Optional[List[str]]): - self.__suggestions = value or [] - - # on_select - @property - def on_select(self) -> OptionalEventCallable["AutoCompleteSelectEvent"]: - return self.__on_select.handler - - @on_select.setter - def on_select(self, handler: OptionalEventCallable["AutoCompleteSelectEvent"]): - self.__on_select.handler = handler - - -class AutoCompleteSelectEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.selection: AutoCompleteSuggestion = AutoCompleteSuggestion( - key=d.get("key"), value=d.get("value") - ) diff --git a/sdk/python/packages/flet/src/flet/core/badge.py b/sdk/python/packages/flet/src/flet/core/badge.py deleted file mode 100644 index fb3e65b5f..000000000 --- a/sdk/python/packages/flet/src/flet/core/badge.py +++ /dev/null @@ -1,26 +0,0 @@ -from dataclasses import dataclass -from typing import Optional, Union - -from flet.core.alignment import Alignment -from flet.core.text_style import TextStyle -from flet.core.types import ColorValue, OffsetValue, OptionalNumber, PaddingValue - - -@dataclass -class Badge: - """Badges are used to show notifications, counts, or status information on navigation items such as NavigationBar or NavigationRail destinations - or a button's icon.""" - - text: Optional[str] = None - offset: Optional[OffsetValue] = None - alignment: Optional[Alignment] = None - bgcolor: Optional[ColorValue] = None - label_visible: Optional[bool] = None - large_size: OptionalNumber = None - padding: Optional[PaddingValue] = None - small_size: OptionalNumber = None - text_color: Optional[ColorValue] = None - text_style: Optional[TextStyle] = None - - -BadgeValue = Union[str, "Badge"] diff --git a/sdk/python/packages/flet/src/flet/core/banner.py b/sdk/python/packages/flet/src/flet/core/banner.py deleted file mode 100644 index f21ddb05c..000000000 --- a/sdk/python/packages/flet/src/flet/core/banner.py +++ /dev/null @@ -1,293 +0,0 @@ -from typing import Any, List, Optional - -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.types import ( - ColorEnums, - ColorValue, - MarginValue, - OptionalControlEventCallable, - PaddingValue, -) - - -class Banner(Control): - """ - A banner displays an important, succinct message, and provides actions for users to address (or dismiss the banner). A user action is required for it to be dismissed. - - Banners are displayed at the top of the screen, below a top app bar. They are persistent and non-modal, allowing the user to either ignore them or interact with them at any time. - - Example: - ``` - import flet as ft - - - def main(page): - page.horizontal_alignment = ft.CrossAxisAlignment.CENTER - - def close_banner(e): - page.close(banner) - page.add(ft.Text("Action clicked: " + e.control.text)) - - action_button_style = ft.ButtonStyle(color=ft.colors.BLUE) - banner = ft.Banner( - bgcolor=ft.colors.AMBER_100, - leading=ft.Icon(ft.icons.WARNING_AMBER_ROUNDED, color=ft.colors.AMBER, size=40), - content=ft.Text( - value="Oops, there were some errors while trying to delete the file. What would you like me to do?", - color=ft.colors.BLACK, - ), - actions=[ - ft.TextButton(text="Retry", style=action_button_style, on_click=close_banner), - ft.TextButton(text="Ignore", style=action_button_style, on_click=close_banner), - ft.TextButton(text="Cancel", style=action_button_style, on_click=close_banner), - ], - ) - - page.add(ft.ElevatedButton("Show Banner", on_click=lambda e: page.open(banner))) - - - ft.app(main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/banner - """ - - def __init__( - self, - content: Control, - actions: List[Control], - open: bool = False, - leading: Optional[Control] = None, - leading_padding: Optional[PaddingValue] = None, - content_padding: Optional[PaddingValue] = None, - force_actions_below: Optional[bool] = None, - bgcolor: Optional[ColorValue] = None, - surface_tint_color: Optional[ColorValue] = None, - shadow_color: Optional[ColorValue] = None, - divider_color: Optional[ColorValue] = None, - elevation: OptionalNumber = None, - margin: Optional[MarginValue] = None, - content_text_style: Optional[TextStyle] = None, - min_action_bar_height: OptionalNumber = None, - on_visible: OptionalControlEventCallable = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - self.open = open - self.leading = leading - self.leading_padding = leading_padding - self.content = content - self.content_padding = content_padding - self.actions = actions - self.force_actions_below = force_actions_below - self.bgcolor = bgcolor - self.surface_tint_color = surface_tint_color - self.shadow_color = shadow_color - self.divider_color = divider_color - self.elevation = elevation - self.margin = margin - self.content_text_style = content_text_style - self.min_action_bar_height = min_action_bar_height - self.on_visible = on_visible - - def _get_control_name(self): - return "banner" - - def before_update(self): - super().before_update() - assert self.__content.visible, "content must be visible" - assert any( - a.visible for a in self.__actions - ), "actions must contain at minimum one visible action Control" - - self._set_attr_json("contentPadding", self.__content_padding) - self._set_attr_json("leadingPadding", self.__leading_padding) - self._set_attr_json("margin", self.__margin) - if isinstance(self.__content_text_style, TextStyle): - self._set_attr_json("contentTextStyle", self.__content_text_style) - - def _get_children(self): - self.__content._set_attr_internal("n", "content") - for action in self.__actions: - action._set_attr_internal("n", "action") - children = [self.__content] + self.__actions - if self.__leading: - self.__leading._set_attr_internal("n", "leading") - children.append(self.__leading) - return children - - # open - @property - def open(self) -> bool: - return self._get_attr("open", data_type="bool", def_value=False) - - @open.setter - def open(self, value: Optional[bool]): - self._set_attr("open", value) - - # modal - @property - def modal(self) -> bool: - return self._get_attr("modal", data_type="bool", def_value=False) - - @modal.setter - def modal(self, value: Optional[bool]): - self._set_attr("modal", value) - - # leading - @property - def leading(self) -> Optional[Control]: - return self.__leading - - @leading.setter - def leading(self, value: Optional[Control]): - self.__leading = value - - # leading_padding - @property - def leading_padding(self) -> Optional[PaddingValue]: - return self.__leading_padding - - @leading_padding.setter - def leading_padding(self, value: Optional[PaddingValue]): - self.__leading_padding = value - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # content_padding - @property - def content_padding(self) -> Optional[PaddingValue]: - return self.__content_padding - - @content_padding.setter - def content_padding(self, value: Optional[PaddingValue]): - self.__content_padding = value - - # margin - @property - def margin(self) -> Optional[MarginValue]: - return self.__margin - - @margin.setter - def margin(self, value: Optional[MarginValue]): - self.__margin = value - - # actions - @property - def actions(self) -> List[Control]: - return self.__actions - - @actions.setter - def actions(self, value: List[Control]): - self.__actions = value - - # force_actions_below - @property - def force_actions_below(self) -> bool: - return self._get_attr("forceActionsBelow", data_type="bool", def_value=False) - - @force_actions_below.setter - def force_actions_below(self, value: Optional[bool]): - self._set_attr("forceActionsBelow", value) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgColor", value, ColorEnums) - - # content_text_style - @property - def content_text_style(self) -> Optional[TextStyle]: - return self.__content_text_style - - @content_text_style.setter - def content_text_style(self, value: Optional[TextStyle]): - self.__content_text_style = value - - # shadow_color - @property - def shadow_color(self) -> Optional[ColorValue]: - return self.__shadow_color - - @shadow_color.setter - def shadow_color(self, value: Optional[ColorValue]): - self.__shadow_color = value - self._set_enum_attr("shadowColor", value, ColorEnums) - - # surface_tint_color - @property - def surface_tint_color(self) -> Optional[ColorValue]: - return self.__surface_tint_color - - @surface_tint_color.setter - def surface_tint_color(self, value: Optional[ColorValue]): - self.__surface_tint_color = value - self._set_enum_attr("surfaceTintColor", value, ColorEnums) - - # divider_color - @property - def divider_color(self) -> Optional[ColorValue]: - return self.__divider_color - - @divider_color.setter - def divider_color(self, value: Optional[ColorValue]): - self.__divider_color = value - self._set_enum_attr("dividerColor", value, ColorEnums) - - # elevation - @property - def elevation(self) -> OptionalNumber: - return self._get_attr("elevation", data_type="float") - - @elevation.setter - def elevation(self, value: OptionalNumber): - assert value is None or value >= 0, "elevation_on_scroll cannot be negative" - self._set_attr("elevation", value) - - # min_action_bar_height - @property - def min_action_bar_height(self) -> OptionalNumber: - return self._get_attr("minActionBarHeight", data_type="float", def_value=52.0) - - @min_action_bar_height.setter - def min_action_bar_height(self, value: OptionalNumber): - self._set_attr("minActionBarHeight", value) - - # on_visible - @property - def on_visible(self) -> OptionalControlEventCallable: - return self._get_event_handler("visible") - - @on_visible.setter - def on_visible(self, handler: OptionalControlEventCallable): - self._add_event_handler("visible", handler) diff --git a/sdk/python/packages/flet/src/flet/core/blur.py b/sdk/python/packages/flet/src/flet/core/blur.py deleted file mode 100644 index fdb91617a..000000000 --- a/sdk/python/packages/flet/src/flet/core/blur.py +++ /dev/null @@ -1,17 +0,0 @@ -from dataclasses import dataclass -from enum import Enum -from typing import Optional - - -class BlurTileMode(Enum): - CLAMP = "clamp" - DECAL = "decal" - MIRROR = "mirror" - REPEATED = "repeated" - - -@dataclass -class Blur: - sigma_x: float - sigma_y: float - tile_mode: Optional[BlurTileMode] = None diff --git a/sdk/python/packages/flet/src/flet/core/border.py b/sdk/python/packages/flet/src/flet/core/border.py deleted file mode 100644 index 74d20c218..000000000 --- a/sdk/python/packages/flet/src/flet/core/border.py +++ /dev/null @@ -1,46 +0,0 @@ -from dataclasses import dataclass -from enum import Enum -from typing import Optional, Union - -from flet.core.types import ColorValue, OptionalNumber - - -class BorderSideStrokeAlign(float, Enum): - INSIDE = -1.0 - CENTER = 0.0 - OUTSIDE = 1.0 - - -@dataclass -class BorderSide: - width: OptionalNumber - color: Optional[ColorValue] = None - stroke_align: Union[BorderSideStrokeAlign, OptionalNumber] = None - - -@dataclass -class Border: - top: Optional[BorderSide] = None - right: Optional[BorderSide] = None - bottom: Optional[BorderSide] = None - left: Optional[BorderSide] = None - - -def all(width: Optional[float] = None, color: Optional[ColorValue] = None) -> Border: - bs = BorderSide(width, color) - return Border(left=bs, top=bs, right=bs, bottom=bs) - - -def symmetric( - vertical: Optional[BorderSide] = None, horizontal: Optional[BorderSide] = None -) -> Border: - return Border(left=horizontal, top=vertical, right=horizontal, bottom=vertical) - - -def only( - left: Optional[BorderSide] = None, - top: Optional[BorderSide] = None, - right: Optional[BorderSide] = None, - bottom: Optional[BorderSide] = None, -) -> Border: - return Border(left=left, top=top, right=right, bottom=bottom) diff --git a/sdk/python/packages/flet/src/flet/core/border_radius.py b/sdk/python/packages/flet/src/flet/core/border_radius.py deleted file mode 100644 index bd82d183c..000000000 --- a/sdk/python/packages/flet/src/flet/core/border_radius.py +++ /dev/null @@ -1,42 +0,0 @@ -import dataclasses -from typing import Union - - -@dataclasses.dataclass -class BorderRadius: - top_left: Union[float, int] - top_right: Union[float, int] - bottom_left: Union[float, int] - bottom_right: Union[float, int] - - -def all(value: float) -> BorderRadius: - return BorderRadius( - top_left=value, top_right=value, bottom_left=value, bottom_right=value - ) - - -def horizontal(left: float = 0, right: float = 0) -> BorderRadius: - return BorderRadius( - top_left=left, top_right=right, bottom_left=left, bottom_right=right - ) - - -def vertical(top: float = 0, bottom: float = 0) -> BorderRadius: - return BorderRadius( - top_left=top, top_right=top, bottom_left=bottom, bottom_right=bottom - ) - - -def only( - top_left: float = 0, - top_right: float = 0, - bottom_left: float = 0, - bottom_right: float = 0, -) -> BorderRadius: - return BorderRadius( - top_left=top_left, - top_right=top_right, - bottom_left=bottom_left, - bottom_right=bottom_right, - ) diff --git a/sdk/python/packages/flet/src/flet/core/bottom_app_bar.py b/sdk/python/packages/flet/src/flet/core/bottom_app_bar.py deleted file mode 100644 index 725910eef..000000000 --- a/sdk/python/packages/flet/src/flet/core/bottom_app_bar.py +++ /dev/null @@ -1,207 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.types import ( - ClipBehavior, - ColorEnums, - ColorValue, - NotchShape, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class BottomAppBar(ConstrainedControl): - """ - A material design bottom app bar. - - ----- - - Online docs: https://flet.dev/docs/controls/bottomappbar - """ - - def __init__( - self, - content: Optional[Control] = None, - surface_tint_color: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, - shadow_color: Optional[ColorValue] = None, - padding: Optional[PaddingValue] = None, - clip_behavior: Optional[ClipBehavior] = None, - shape: Optional[NotchShape] = None, - notch_margin: OptionalNumber = None, - elevation: OptionalNumber = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - ) - - self.content = content - self.surface_tint_color = surface_tint_color - self.bgcolor = bgcolor - self.shadow_color = shadow_color - self.padding = padding - self.shape = shape - self.clip_behavior = clip_behavior - self.notch_margin = notch_margin - self.elevation = elevation - - def _get_control_name(self): - return "bottomappbar" - - def before_update(self): - super().before_update() - self._set_attr_json("padding", self.__padding) - - def _get_children(self): - if self.__content is not None: - self.__content._set_attr_internal("n", "content") - return [self.__content] - return [] - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # surface_tint_color - @property - def surface_tint_color(self) -> Optional[ColorValue]: - return self.__surface_tint_color - - @surface_tint_color.setter - def surface_tint_color(self, value: Optional[ColorValue]): - self.__surface_tint_color = value - self._set_enum_attr("surfaceTintColor", value, ColorEnums) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # shadow_color - @property - def shadow_color(self) -> Optional[ColorValue]: - return self.__shadow_color - - @shadow_color.setter - def shadow_color(self, value: Optional[ColorValue]): - self.__shadow_color = value - self._set_enum_attr("shadowColor", value, ColorEnums) - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # shape - @property - def shape(self) -> Optional[NotchShape]: - return self.__shape - - @shape.setter - def shape(self, value: Optional[NotchShape]): - self.__shape = value - self._set_enum_attr("shape", value, NotchShape) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # notch_margin - @property - def notch_margin(self) -> OptionalNumber: - return self._get_attr("notchMargin") - - @notch_margin.setter - def notch_margin(self, value: OptionalNumber): - self._set_attr("notchMargin", value) - - # elevation - @property - def elevation(self) -> OptionalNumber: - return self._get_attr("elevation") - - @elevation.setter - def elevation(self, value: OptionalNumber): - assert value is None or value >= 0, "elevation cannot be negative" - self._set_attr("elevation", value) diff --git a/sdk/python/packages/flet/src/flet/core/bottom_sheet.py b/sdk/python/packages/flet/src/flet/core/bottom_sheet.py deleted file mode 100644 index 789c47dd9..000000000 --- a/sdk/python/packages/flet/src/flet/core/bottom_sheet.py +++ /dev/null @@ -1,255 +0,0 @@ -from typing import Any, Optional - -from flet.core.animation import AnimationStyle -from flet.core.box import BoxConstraints -from flet.core.buttons import OutlinedBorder -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.types import ( - ClipBehavior, - ColorEnums, - ColorValue, - OptionalControlEventCallable, -) - - -class BottomSheet(Control): - """ - A modal bottom sheet is an alternative to a menu or a dialog and prevents the user from interacting with the rest of the app. - - Example: - ``` - import flet as ft - - - def main(page: ft.Page): - page.horizontal_alignment = ft.CrossAxisAlignment.CENTER - - def handle_dismissal(e): - page.add(ft.Text("Bottom sheet dismissed")) - bs = ft.BottomSheet( - on_dismiss=handle_dismissal, - content=ft.Container( - padding=50, - content=ft.Column( - tight=True, - controls=[ - ft.Text("This is bottom sheet's content!"), - ft.ElevatedButton("Close bottom sheet", on_click=lambda _: page.close(bs)), - ], - ), - ), - ) - page.add(ft.ElevatedButton("Display bottom sheet", on_click=lambda _: page.open(bs))) - - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/bottomsheet - """ - - def __init__( - self, - content: Control, - open: bool = False, - elevation: OptionalNumber = None, - bgcolor: Optional[ColorValue] = None, - dismissible: Optional[bool] = None, - enable_drag: Optional[bool] = None, - show_drag_handle: Optional[bool] = None, - use_safe_area: Optional[bool] = None, - is_scroll_controlled: Optional[bool] = None, - maintain_bottom_view_insets_padding: Optional[bool] = None, - animation_style: Optional[AnimationStyle] = None, - size_constraints: Optional[BoxConstraints] = None, - clip_behavior: Optional[ClipBehavior] = None, - shape: Optional[OutlinedBorder] = None, - on_dismiss: OptionalControlEventCallable = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - self.open = open - self.elevation = elevation - self.bgcolor = bgcolor - self.dismissible = dismissible - self.enable_drag = enable_drag - self.show_drag_handle = show_drag_handle - self.use_safe_area = use_safe_area - self.is_scroll_controlled = is_scroll_controlled - self.content = content - self.maintain_bottom_view_insets_padding = maintain_bottom_view_insets_padding - self.animation_style = animation_style - self.size_constraints = size_constraints - self.clip_behavior = clip_behavior - self.shape = shape - self.on_dismiss = on_dismiss - - def _get_control_name(self): - return "bottomsheet" - - def _get_children(self): - self.__content._set_attr_internal("n", "content") - return [self.__content] - - def before_update(self): - super().before_update() - assert self.__content.visible, "content must be visible" - self._set_attr_json("animationStyle", self.__animation_style) - self._set_attr_json("sizeConstraints", self.__size_constraints) - self._set_attr_json("shape", self.__shape) - - # open - @property - def open(self) -> bool: - return self._get_attr("open", data_type="bool", def_value=False) - - @open.setter - def open(self, value: Optional[bool]): - self._set_attr("open", value) - - # elevation - @property - def elevation(self) -> OptionalNumber: - return self._get_attr("elevation") - - @elevation.setter - def elevation(self, value: OptionalNumber): - assert value is None or value >= 0, "elevation cannot be negative" - self._set_attr("elevation", value) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgColor", value, ColorEnums) - - # dismissible - @property - def dismissible(self) -> bool: - return self._get_attr("dismissible", data_type="bool", def_value=True) - - @dismissible.setter - def dismissible(self, value: Optional[bool]): - self._set_attr("dismissible", value) - - # enable_drag - @property - def enable_drag(self) -> bool: - return self._get_attr("enableDrag", data_type="bool", def_value=False) - - @enable_drag.setter - def enable_drag(self, value: Optional[bool]): - self._set_attr("enableDrag", value) - - # show_drag_handle - @property - def show_drag_handle(self) -> bool: - return self._get_attr("showDragHandle", data_type="bool", def_value=False) - - @show_drag_handle.setter - def show_drag_handle(self, value: Optional[bool]): - self._set_attr("showDragHandle", value) - - # use_safe_area - @property - def use_safe_area(self) -> bool: - return self._get_attr("useSafeArea", data_type="bool", def_value=True) - - @use_safe_area.setter - def use_safe_area(self, value: Optional[bool]): - self._set_attr("useSafeArea", value) - - # is_scroll_controlled - @property - def is_scroll_controlled(self) -> bool: - return self._get_attr("isScrollControlled", data_type="bool", def_value=False) - - @is_scroll_controlled.setter - def is_scroll_controlled(self, value: Optional[bool]): - self._set_attr("isScrollControlled", value) - - # maintain_bottom_view_insets_padding - @property - def maintain_bottom_view_insets_padding(self) -> bool: - return self._get_attr( - "maintainBottomViewInsetsPadding", data_type="bool", def_value=True - ) - - @maintain_bottom_view_insets_padding.setter - def maintain_bottom_view_insets_padding(self, value: Optional[bool]): - self._set_attr("maintainBottomViewInsetsPadding", value) - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # animation_style - @property - def animation_style(self) -> Optional[AnimationStyle]: - return self.__animation_style - - @animation_style.setter - def animation_style(self, value: Optional[AnimationStyle]): - self.__animation_style = value - - # size_constraints - @property - def size_constraints(self) -> Optional[BoxConstraints]: - return self.__size_constraints - - @size_constraints.setter - def size_constraints(self, value: Optional[BoxConstraints]): - self.__size_constraints = value - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # shape - @property - def shape(self) -> Optional[OutlinedBorder]: - return self.__shape - - @shape.setter - def shape(self, value: Optional[OutlinedBorder]): - self.__shape = value - - # on_dismiss - @property - def on_dismiss(self) -> OptionalControlEventCallable: - return self._get_event_handler("dismiss") - - @on_dismiss.setter - def on_dismiss(self, handler: OptionalControlEventCallable): - self._add_event_handler("dismiss", handler) diff --git a/sdk/python/packages/flet/src/flet/core/box.py b/sdk/python/packages/flet/src/flet/core/box.py deleted file mode 100644 index d193bc2fb..000000000 --- a/sdk/python/packages/flet/src/flet/core/box.py +++ /dev/null @@ -1,95 +0,0 @@ -from dataclasses import dataclass, field -from enum import Enum -from typing import List, Optional, Union - -from flet.core.alignment import Alignment -from flet.core.border import Border -from flet.core.gradients import Gradient -from flet.core.types import ( - BlendMode, - BorderRadiusValue, - ColorValue, - ImageFit, - ImageRepeat, - Number, - OffsetValue, - OptionalNumber, -) - - -@dataclass -class ColorFilter: - color: Optional[ColorValue] = None - blend_mode: Optional[BlendMode] = None - - -class FilterQuality(Enum): - NONE = "none" - LOW = "low" - MEDIUM = "medium" - HIGH = "high" - - -class ShadowBlurStyle(Enum): - NORMAL = "normal" - SOLID = "solid" - OUTER = "outer" - INNER = "inner" - - -@dataclass -class BoxShadow: - spread_radius: Optional[float] = None - blur_radius: Optional[float] = None - color: Optional[ColorValue] = None - offset: Optional[OffsetValue] = None - blur_style: ShadowBlurStyle = field(default=ShadowBlurStyle.NORMAL) - - -class BoxShape(Enum): - RECTANGLE = "rectangle" - CIRCLE = "circle" - - -@dataclass -class DecorationImage: - src: Optional[str] = None - src_base64: Optional[str] = None - color_filter: Optional[ColorFilter] = None - fit: Optional[ImageFit] = None - alignment: Optional[Alignment] = None - repeat: Optional[ImageRepeat] = None - match_text_direction: Optional[bool] = None - scale: OptionalNumber = None - opacity: OptionalNumber = None - filter_quality: Optional[FilterQuality] = None - invert_colors: Optional[bool] = None - anti_alias: Optional[bool] = None - - -@dataclass -class BoxDecoration: - bgcolor: Optional[ColorValue] = None - image: Optional[DecorationImage] = None - border: Optional[Border] = None - border_radius: Optional[BorderRadiusValue] = None - shadow: Union[None, BoxShadow, List[BoxShadow]] = None - gradient: Optional[Gradient] = None - shape: Optional[BoxShape] = None - blend_mode: Optional[BlendMode] = None - - -@dataclass -class BoxConstraints: - min_width: Number = 0 - min_height: Number = 0 - max_width: Number = float("inf") - max_height: Number = float("inf") - - def __post_init__(self): - assert ( - 0 <= self.min_width <= self.max_width <= float("inf") - ), "min_width and max_width must be between 0 and infinity and min_width must be less than or equal to max_width" - assert ( - 0 <= self.min_height <= self.max_height <= float("inf") - ), "min_height and max_height must be between 0 and infinity and min_height must be less than or equal to max_height" diff --git a/sdk/python/packages/flet/src/flet/core/button.py b/sdk/python/packages/flet/src/flet/core/button.py deleted file mode 100644 index 7ff766b0c..000000000 --- a/sdk/python/packages/flet/src/flet/core/button.py +++ /dev/null @@ -1,25 +0,0 @@ -from flet.core.elevated_button import ElevatedButton - - -class Button(ElevatedButton): - """ - Elevated buttons or Buttons are essentially filled tonal buttons with a shadow. To prevent shadow creep, only use them when absolutely necessary, such as when the button requires visual separation from a patterned background. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.title = "Basic buttons" - page.add( - ft.Button(text="Button"), - ft.Button("Disabled button", disabled=True), - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/elevatedbutton - """ diff --git a/sdk/python/packages/flet/src/flet/core/buttons.py b/sdk/python/packages/flet/src/flet/core/buttons.py deleted file mode 100644 index 61c19a215..000000000 --- a/sdk/python/packages/flet/src/flet/core/buttons.py +++ /dev/null @@ -1,91 +0,0 @@ -from dataclasses import dataclass -from typing import Optional - -from flet.core.alignment import Alignment -from flet.core.border import BorderSide -from flet.core.text_style import TextStyle -from flet.core.types import ( - BorderRadiusValue, - ColorValue, - ControlState, - ControlStateValue, - MouseCursor, - OptionalNumber, - PaddingValue, - VisualDensity, -) - - -@dataclass -class OutlinedBorder: - pass - - -@dataclass -class StadiumBorder(OutlinedBorder): - def __post_init__(self): - self.type = "stadium" - - -@dataclass -class RoundedRectangleBorder(OutlinedBorder): - radius: Optional[BorderRadiusValue] = None - - def __post_init__(self): - self.type = "roundedRectangle" - - -@dataclass -class CircleBorder(OutlinedBorder): - def __post_init__(self): - self.type = "circle" - - -@dataclass -class BeveledRectangleBorder(OutlinedBorder): - radius: Optional[BorderRadiusValue] = None - - def __post_init__(self): - self.type = "beveledRectangle" - - -@dataclass -class ContinuousRectangleBorder(OutlinedBorder): - radius: Optional[BorderRadiusValue] = None - - def __post_init__(self): - self.type = "continuousRectangle" - - -@dataclass -class ButtonStyle: - color: ControlStateValue[ColorValue] = None - bgcolor: ControlStateValue[ColorValue] = None - overlay_color: ControlStateValue[ColorValue] = None - shadow_color: ControlStateValue[ColorValue] = None - surface_tint_color: ControlStateValue[ColorValue] = None - elevation: ControlStateValue[OptionalNumber] = None - animation_duration: Optional[int] = None - padding: ControlStateValue[PaddingValue] = None - side: ControlStateValue[BorderSide] = None - shape: ControlStateValue[OutlinedBorder] = None - alignment: Optional[Alignment] = None - enable_feedback: Optional[bool] = None - text_style: ControlStateValue[TextStyle] = None - icon_size: ControlStateValue[OptionalNumber] = None - icon_color: ControlStateValue[ColorValue] = None - visual_density: Optional[VisualDensity] = None - mouse_cursor: ControlStateValue[MouseCursor] = None - - def __post_init__(self): - if not isinstance(self.text_style, dict): - self.text_style = {ControlState.DEFAULT: self.text_style} - - if not isinstance(self.padding, dict): - self.padding = {ControlState.DEFAULT: self.padding} - - if not isinstance(self.side, dict): - self.side = {ControlState.DEFAULT: self.side} - - if not isinstance(self.shape, dict): - self.shape = {ControlState.DEFAULT: self.shape} diff --git a/sdk/python/packages/flet/src/flet/core/canvas/arc.py b/sdk/python/packages/flet/src/flet/core/canvas/arc.py deleted file mode 100644 index 92cce47d4..000000000 --- a/sdk/python/packages/flet/src/flet/core/canvas/arc.py +++ /dev/null @@ -1,115 +0,0 @@ -from typing import Any, Optional - -from flet.core.canvas.shape import Shape -from flet.core.control import OptionalNumber -from flet.core.painting import Paint - - -class Arc(Shape): - def __init__( - self, - x: OptionalNumber = None, - y: OptionalNumber = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - start_angle: OptionalNumber = None, - sweep_angle: OptionalNumber = None, - use_center: Optional[bool] = None, - paint: Optional[Paint] = None, - # - # Control - # - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Shape.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) - - self.x = x - self.y = y - self.width = width - self.height = height - self.start_angle = start_angle - self.sweep_angle = sweep_angle - self.use_center = use_center - self.paint = paint - - def _get_control_name(self): - return "arc" - - def before_update(self): - super().before_update() - self._set_attr_json("paint", self.__paint) - - # x - @property - def x(self) -> OptionalNumber: - return self._get_attr("x") - - @x.setter - def x(self, value: OptionalNumber): - self._set_attr("x", value) - - # y - @property - def y(self) -> OptionalNumber: - return self._get_attr("y") - - @y.setter - def y(self, value: OptionalNumber): - self._set_attr("y", value) - - # width - @property - def width(self) -> OptionalNumber: - return self._get_attr("width") - - @width.setter - def width(self, value: OptionalNumber): - self._set_attr("width", value) - - # height - @property - def height(self) -> OptionalNumber: - return self._get_attr("height") - - @height.setter - def height(self, value: OptionalNumber): - self._set_attr("height", value) - - # start_angle - @property - def start_angle(self) -> OptionalNumber: - return self._get_attr("startAngle") - - @start_angle.setter - def start_angle(self, value: OptionalNumber): - self._set_attr("startAngle", value) - - # sweep_angle - @property - def sweep_angle(self) -> OptionalNumber: - return self._get_attr("sweepAngle") - - @sweep_angle.setter - def sweep_angle(self, value: OptionalNumber): - self._set_attr("sweepAngle", value) - - # use_center - @property - def use_center(self) -> bool: - return self._get_attr("useCenter", data_type="bool", def_value=False) - - @use_center.setter - def use_center(self, value: Optional[bool]): - self._set_attr("useCenter", value) - - # paint - @property - def paint(self) -> Optional[Paint]: - return self.__paint - - @paint.setter - def paint(self, value: Optional[Paint]): - self.__paint = value diff --git a/sdk/python/packages/flet/src/flet/core/canvas/canvas.py b/sdk/python/packages/flet/src/flet/core/canvas/canvas.py deleted file mode 100644 index 065dc95d1..000000000 --- a/sdk/python/packages/flet/src/flet/core/canvas/canvas.py +++ /dev/null @@ -1,152 +0,0 @@ -import json -from typing import Any, List, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.canvas.shape import Shape -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.types import ( - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class Canvas(ConstrainedControl): - def __init__( - self, - shapes: Optional[List[Shape]] = None, - content: Optional[Control] = None, - resize_interval: OptionalNumber = None, - on_resize=None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - ) - - self.__on_resize = EventHandler(lambda e: CanvasResizeEvent(e)) - self._add_event_handler("resize", self.__on_resize.get_handler()) - - self.shapes = shapes - self.content = content - self.resize_interval = resize_interval - self.on_resize = on_resize - - def _get_control_name(self): - return "canvas" - - def _get_children(self): - children = [] - children.extend(self.__shapes) - if self.__content is not None: - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - return children - - def clean(self): - super().clean() - self.__shapes.clear() - - # shapes - @property - def shapes(self) -> List[Shape]: - return self.__shapes - - @shapes.setter - def shapes(self, value: Optional[List[Shape]]): - self.__shapes = value if value is not None else [] - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # resize_interval - @property - def resize_interval(self) -> OptionalNumber: - return self._get_attr("resizeInterval") - - @resize_interval.setter - def resize_interval(self, value: OptionalNumber): - self._set_attr("resizeInterval", value) - - # on_resize - @property - def on_resize(self) -> OptionalEventCallable["CanvasResizeEvent"]: - return self.__on_resize.handler - - @on_resize.setter - def on_resize(self, handler: OptionalEventCallable["CanvasResizeEvent"]): - self.__on_resize.handler = handler - self._set_attr("onresize", True if handler is not None else None) - - -class CanvasResizeEvent(ControlEvent): - def __init__(self, e: ControlEvent) -> None: - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.width: float = d.get("w") - self.height: float = d.get("h") diff --git a/sdk/python/packages/flet/src/flet/core/canvas/circle.py b/sdk/python/packages/flet/src/flet/core/canvas/circle.py deleted file mode 100644 index d9a6a4a38..000000000 --- a/sdk/python/packages/flet/src/flet/core/canvas/circle.py +++ /dev/null @@ -1,71 +0,0 @@ -from typing import Any, Optional - -from flet.core.canvas.shape import Shape -from flet.core.control import OptionalNumber -from flet.core.painting import Paint - - -class Circle(Shape): - def __init__( - self, - x: OptionalNumber = None, - y: OptionalNumber = None, - radius: OptionalNumber = None, - paint: Optional[Paint] = None, - # - # Control - # - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Shape.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) - - self.x = x - self.y = y - self.radius = radius - self.paint = paint - - def _get_control_name(self): - return "circle" - - def before_update(self): - super().before_update() - self._set_attr_json("paint", self.__paint) - - # x - @property - def x(self) -> OptionalNumber: - return self._get_attr("x") - - @x.setter - def x(self, value: OptionalNumber): - self._set_attr("x", value) - - # y - @property - def y(self) -> OptionalNumber: - return self._get_attr("y") - - @y.setter - def y(self, value: OptionalNumber): - self._set_attr("y", value) - - # radius - @property - def radius(self) -> OptionalNumber: - return self._get_attr("radius") - - @radius.setter - def radius(self, value: OptionalNumber): - self._set_attr("radius", value) - - # paint - @property - def paint(self) -> Optional[Paint]: - return self.__paint - - @paint.setter - def paint(self, value: Optional[Paint]): - self.__paint = value diff --git a/sdk/python/packages/flet/src/flet/core/canvas/color.py b/sdk/python/packages/flet/src/flet/core/canvas/color.py deleted file mode 100644 index 00f5225c2..000000000 --- a/sdk/python/packages/flet/src/flet/core/canvas/color.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import Any, Optional - -from flet.core.canvas.shape import Shape -from flet.core.types import BlendMode, ColorEnums, ColorValue - - -class Color(Shape): - def __init__( - self, - color: Optional[ColorValue] = None, - blend_mode: Optional[BlendMode] = None, - # - # Control - # - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Shape.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) - - self.color = color - self.blend_mode = blend_mode - - def _get_control_name(self): - return "color" - - def before_update(self): - super().before_update() - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # blend_mode - @property - def blend_mode(self) -> Optional[BlendMode]: - return self.__blend_mode - - @blend_mode.setter - def blend_mode(self, value: Optional[BlendMode]): - self.__blend_mode = value - self._set_attr( - "blendMode", value.value if isinstance(value, BlendMode) else value - ) diff --git a/sdk/python/packages/flet/src/flet/core/canvas/fill.py b/sdk/python/packages/flet/src/flet/core/canvas/fill.py deleted file mode 100644 index 41cdf7b01..000000000 --- a/sdk/python/packages/flet/src/flet/core/canvas/fill.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Any, Optional - -from flet.core.canvas.shape import Shape -from flet.core.painting import Paint - - -class Fill(Shape): - def __init__( - self, - paint: Optional[Paint] = None, - # - # Control - # - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Shape.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) - - self.paint = paint - - def _get_control_name(self): - return "fill" - - def before_update(self): - super().before_update() - self._set_attr_json("paint", self.__paint) - - # paint - @property - def paint(self) -> Optional[Paint]: - return self.__paint - - @paint.setter - def paint(self, value: Optional[Paint]): - self.__paint = value diff --git a/sdk/python/packages/flet/src/flet/core/canvas/line.py b/sdk/python/packages/flet/src/flet/core/canvas/line.py deleted file mode 100644 index 5e60d1881..000000000 --- a/sdk/python/packages/flet/src/flet/core/canvas/line.py +++ /dev/null @@ -1,82 +0,0 @@ -from typing import Any, Optional - -from flet.core.canvas.shape import Shape -from flet.core.control import OptionalNumber -from flet.core.painting import Paint - - -class Line(Shape): - def __init__( - self, - x1: OptionalNumber = None, - y1: OptionalNumber = None, - x2: OptionalNumber = None, - y2: OptionalNumber = None, - paint: Optional[Paint] = None, - # - # Control - # - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Shape.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) - - self.x1 = x1 - self.y1 = y1 - self.x2 = x2 - self.y2 = y2 - self.paint = paint - - def _get_control_name(self): - return "line" - - def before_update(self): - super().before_update() - self._set_attr_json("paint", self.__paint) - - # x1 - @property - def x1(self) -> OptionalNumber: - return self._get_attr("x1") - - @x1.setter - def x1(self, value: OptionalNumber): - self._set_attr("x1", value) - - # y1 - @property - def y1(self) -> OptionalNumber: - return self._get_attr("y1") - - @y1.setter - def y1(self, value: OptionalNumber): - self._set_attr("y1", value) - - # x2 - @property - def x2(self) -> OptionalNumber: - return self._get_attr("x2") - - @x2.setter - def x2(self, value: OptionalNumber): - self._set_attr("x2", value) - - # y2 - @property - def y2(self) -> OptionalNumber: - return self._get_attr("y2") - - @y2.setter - def y2(self, value: OptionalNumber): - self._set_attr("y2", value) - - # paint - @property - def paint(self) -> Optional[Paint]: - return self.__paint - - @paint.setter - def paint(self, value: Optional[Paint]): - self.__paint = value diff --git a/sdk/python/packages/flet/src/flet/core/canvas/oval.py b/sdk/python/packages/flet/src/flet/core/canvas/oval.py deleted file mode 100644 index 2c0bf0261..000000000 --- a/sdk/python/packages/flet/src/flet/core/canvas/oval.py +++ /dev/null @@ -1,80 +0,0 @@ -from typing import Any, Optional - -from flet.core.canvas.shape import Shape -from flet.core.control import OptionalNumber -from flet.core.painting import Paint - - -class Oval(Shape): - def __init__( - self, - x: OptionalNumber = None, - y: OptionalNumber = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - paint: Optional[Paint] = None, - # base - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Shape.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) - - self.x = x - self.y = y - self.width = width - self.height = height - self.paint = paint - - def _get_control_name(self): - return "oval" - - def before_update(self): - super().before_update() - self._set_attr_json("paint", self.__paint) - - # x - @property - def x(self) -> OptionalNumber: - return self._get_attr("x") - - @x.setter - def x(self, value: OptionalNumber): - self._set_attr("x", value) - - # y - @property - def y(self) -> OptionalNumber: - return self._get_attr("y") - - @y.setter - def y(self, value: OptionalNumber): - self._set_attr("y", value) - - # width - @property - def width(self) -> OptionalNumber: - return self._get_attr("width") - - @width.setter - def width(self, value: OptionalNumber): - self._set_attr("width", value) - - # height - @property - def height(self) -> OptionalNumber: - return self._get_attr("height") - - @height.setter - def height(self, value: OptionalNumber): - self._set_attr("height", value) - - # paint - @property - def paint(self) -> Optional[Paint]: - return self.__paint - - @paint.setter - def paint(self, value: Optional[Paint]): - self.__paint = value diff --git a/sdk/python/packages/flet/src/flet/core/canvas/path.py b/sdk/python/packages/flet/src/flet/core/canvas/path.py deleted file mode 100644 index 5c5580d3d..000000000 --- a/sdk/python/packages/flet/src/flet/core/canvas/path.py +++ /dev/null @@ -1,134 +0,0 @@ -import dataclasses -from typing import Any, List, Optional - -from flet.core.canvas.shape import Shape -from flet.core.painting import Paint -from flet.core.types import BorderRadiusValue - - -class Path(Shape): - @dataclasses.dataclass - class PathElement: - pass - - @dataclasses.dataclass - class MoveTo(PathElement): - x: float - y: float - type: str = dataclasses.field(default="moveto") - - @dataclasses.dataclass - class LineTo(PathElement): - x: float - y: float - type: str = dataclasses.field(default="lineto") - - @dataclasses.dataclass - class QuadraticTo(PathElement): - cp1x: float - cp1y: float - x: float - y: float - w: float = dataclasses.field(default=1) - type: str = dataclasses.field(default="conicto") - - @dataclasses.dataclass - class CubicTo(PathElement): - cp1x: float - cp1y: float - cp2x: float - cp2y: float - x: float - y: float - type: str = dataclasses.field(default="cubicto") - - @dataclasses.dataclass - class SubPath(PathElement): - elements: List["Path.PathElement"] - x: float - y: float - type: str = dataclasses.field(default="subpath") - - @dataclasses.dataclass - class Arc(PathElement): - x: float - y: float - width: float - height: float - start_angle: float - sweep_angle: float - type: str = dataclasses.field(default="arc") - - @dataclasses.dataclass - class ArcTo(PathElement): - x: float - y: float - radius: float = dataclasses.field(default=0) - rotation: float = dataclasses.field(default=0) - large_arc: bool = dataclasses.field(default=False) - clockwise: bool = dataclasses.field(default=True) - type: str = dataclasses.field(default="arcto") - - @dataclasses.dataclass - class Oval(PathElement): - x: float - y: float - width: float - height: float - type: str = dataclasses.field(default="oval") - - @dataclasses.dataclass - class Rect(PathElement): - x: float - y: float - width: float - height: float - border_radius: Optional[BorderRadiusValue] = None - type: str = dataclasses.field(default="rect") - - @dataclasses.dataclass - class Close(PathElement): - type: str = dataclasses.field(default="close") - - def __init__( - self, - elements: Optional[List[PathElement]] = None, - paint: Optional[Paint] = None, - # - # Control - # - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Shape.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) - - self.elements = elements - self.paint = paint - - def _get_control_name(self): - return "path" - - def before_update(self): - super().before_update() - self._set_attr_json("elements", self.__elements) - self._set_attr_json("paint", self.__paint) - - # elements - @property - def elements(self): - return self.__elements - - @elements.setter - def elements(self, value: Optional[List[PathElement]]): - self.__elements = value if value is not None else [] - - # paint - @property - def paint(self) -> Optional[Paint]: - return self.__paint - - @paint.setter - def paint(self, value: Optional[Paint]): - self.__paint = value diff --git a/sdk/python/packages/flet/src/flet/core/canvas/points.py b/sdk/python/packages/flet/src/flet/core/canvas/points.py deleted file mode 100644 index 81def6ecb..000000000 --- a/sdk/python/packages/flet/src/flet/core/canvas/points.py +++ /dev/null @@ -1,71 +0,0 @@ -from enum import Enum -from typing import Any, List, Optional - -from flet.core.canvas.shape import Shape -from flet.core.painting import Paint -from flet.core.types import OffsetValue - - -class PointMode(Enum): - POINTS = "points" - LINES = "lines" - POLYGON = "polygon" - - -class Points(Shape): - def __init__( - self, - points: Optional[List[OffsetValue]] = None, - point_mode: Optional[PointMode] = None, - paint: Optional[Paint] = None, - # - # Control - # - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Shape.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) - - self.points = points - self.point_mode = point_mode - self.paint = paint - - def _get_control_name(self): - return "points" - - def before_update(self): - super().before_update() - self._set_attr_json("points", self.__points) - self._set_attr_json("paint", self.__paint) - - # point_mode - @property - def point_mode(self) -> Optional[PointMode]: - return self.__point_mode - - @point_mode.setter - def point_mode(self, value: Optional[PointMode]): - self.__point_mode = value - self._set_attr( - "pointMode", value.value if isinstance(value, PointMode) else value - ) - - # points - @property - def points(self) -> Optional[List[OffsetValue]]: - return self.__points - - @points.setter - def points(self, value: Optional[List[OffsetValue]]): - self.__points = value if value is not None else [] - - # paint - @property - def paint(self) -> Optional[Paint]: - return self.__paint - - @paint.setter - def paint(self, value: Optional[Paint]): - self.__paint = value diff --git a/sdk/python/packages/flet/src/flet/core/canvas/rect.py b/sdk/python/packages/flet/src/flet/core/canvas/rect.py deleted file mode 100644 index d775485d0..000000000 --- a/sdk/python/packages/flet/src/flet/core/canvas/rect.py +++ /dev/null @@ -1,93 +0,0 @@ -from typing import Any, Optional - -from flet.core.canvas.shape import Shape -from flet.core.control import OptionalNumber -from flet.core.painting import Paint -from flet.core.types import BorderRadiusValue - - -class Rect(Shape): - def __init__( - self, - x: OptionalNumber = None, - y: OptionalNumber = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - border_radius: Optional[BorderRadiusValue] = None, - paint: Optional[Paint] = None, - # base - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Shape.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) - - self.x = x - self.y = y - self.width = width - self.height = height - self.border_radius = border_radius - self.paint = paint - - def _get_control_name(self): - return "rect" - - def before_update(self): - super().before_update() - self._set_attr_json("borderRadius", self.__border_radius) - self._set_attr_json("paint", self.__paint) - - # x - @property - def x(self) -> OptionalNumber: - return self._get_attr("x") - - @x.setter - def x(self, value: OptionalNumber): - self._set_attr("x", value) - - # y - @property - def y(self) -> OptionalNumber: - return self._get_attr("y") - - @y.setter - def y(self, value: OptionalNumber): - self._set_attr("y", value) - - # width - @property - def width(self) -> OptionalNumber: - return self._get_attr("width") - - @width.setter - def width(self, value: OptionalNumber): - self._set_attr("width", value) - - # height - @property - def height(self) -> OptionalNumber: - return self._get_attr("height") - - @height.setter - def height(self, value: OptionalNumber): - self._set_attr("height", value) - - # border_radius - @property - def border_radius(self) -> Optional[BorderRadiusValue]: - return self.__border_radius - - @border_radius.setter - def border_radius(self, value: Optional[BorderRadiusValue]): - self.__border_radius = value - - # paint - @property - def paint(self) -> Optional[Paint]: - return self.__paint - - @paint.setter - def paint(self, value: Optional[Paint]): - self.__paint = value diff --git a/sdk/python/packages/flet/src/flet/core/canvas/shadow.py b/sdk/python/packages/flet/src/flet/core/canvas/shadow.py deleted file mode 100644 index f983e8056..000000000 --- a/sdk/python/packages/flet/src/flet/core/canvas/shadow.py +++ /dev/null @@ -1,73 +0,0 @@ -from typing import Any, List, Optional - -from flet.core.canvas.path import Path -from flet.core.canvas.shape import Shape -from flet.core.control import OptionalNumber -from flet.core.types import ColorEnums, ColorValue - - -class Shadow(Shape): - def __init__( - self, - path: Optional[List[Path.PathElement]] = None, - color: Optional[ColorValue] = None, - elevation: OptionalNumber = None, - transparent_occluder: Optional[bool] = None, - # - # Control - # - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Shape.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) - - self.path = path - self.color = color - self.elevation = elevation - self.transparent_occluder = transparent_occluder - - def _get_control_name(self): - return "shadow" - - def before_update(self): - super().before_update() - self._set_attr_json("path", self.__path) - - # path - @property - def path(self): - return self.__path - - @path.setter - def path(self, value: Optional[List[Path.PathElement]]): - self.__path = value if value is not None else [] - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # elevation - @property - def elevation(self) -> OptionalNumber: - return self._get_attr("elevation") - - @elevation.setter - def elevation(self, value: OptionalNumber): - self._set_attr("elevation", value) - - # transparent_occluder - @property - def transparent_occluder(self) -> bool: - return self._get_attr("transparentOccluder", data_type="bool", def_value=False) - - @transparent_occluder.setter - def transparent_occluder(self, value: Optional[bool]): - self._set_attr("transparentOccluder", value) diff --git a/sdk/python/packages/flet/src/flet/core/canvas/shape.py b/sdk/python/packages/flet/src/flet/core/canvas/shape.py deleted file mode 100644 index 6f8066f05..000000000 --- a/sdk/python/packages/flet/src/flet/core/canvas/shape.py +++ /dev/null @@ -1,14 +0,0 @@ -from typing import Any, Optional - -from flet.core.control import Control - - -class Shape(Control): - def __init__( - self, - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Control.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) diff --git a/sdk/python/packages/flet/src/flet/core/canvas/text.py b/sdk/python/packages/flet/src/flet/core/canvas/text.py deleted file mode 100644 index 990ce84c2..000000000 --- a/sdk/python/packages/flet/src/flet/core/canvas/text.py +++ /dev/null @@ -1,160 +0,0 @@ -from typing import Any, List, Optional - -from flet.core.alignment import Alignment -from flet.core.canvas.shape import Shape -from flet.core.control import OptionalNumber -from flet.core.inline_span import InlineSpan -from flet.core.text_style import TextStyle -from flet.core.types import TextAlign - - -class Text(Shape): - def __init__( - self, - x: OptionalNumber = None, - y: OptionalNumber = None, - text: Optional[str] = None, - style: Optional[TextStyle] = None, - spans: Optional[List[InlineSpan]] = None, - alignment: Optional[Alignment] = None, - text_align: Optional[TextAlign] = None, - max_lines: Optional[int] = None, - max_width: OptionalNumber = None, - ellipsis: Optional[str] = None, - rotate: OptionalNumber = None, - # - # Control - # - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Shape.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) - - self.x = x - self.y = y - self.text = text - self.style = style - self.spans = spans - self.alignment = alignment - self.text_align = text_align - self.max_lines = max_lines - self.max_width = max_width - self.ellipsis = ellipsis - self.rotate = rotate - - def _get_control_name(self): - return "text" - - def _get_children(self): - children = [] - children.extend(self.__spans) - return children - - def before_update(self): - super().before_update() - self._set_attr_json("style", self.__style) - self._set_attr_json("alignment", self.__alignment) - - # x - @property - def x(self) -> OptionalNumber: - return self._get_attr("x") - - @x.setter - def x(self, value: OptionalNumber): - self._set_attr("x", value) - - # y - @property - def y(self) -> OptionalNumber: - return self._get_attr("y") - - @y.setter - def y(self, value: OptionalNumber): - self._set_attr("y", value) - - # text - @property - def text(self) -> Optional[str]: - return self._get_attr("text") - - @text.setter - def text(self, value: Optional[str]): - self._set_attr("text", value) - - # style - @property - def style(self) -> Optional[TextStyle]: - return self.__style - - @style.setter - def style(self, value: Optional[TextStyle]): - self.__style = value - - # spans - @property - def spans(self) -> Optional[List[InlineSpan]]: - return self.__spans - - @spans.setter - def spans(self, value: Optional[List[InlineSpan]]): - self.__spans = value if value is not None else [] - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value - - # text_align - @property - def text_align(self) -> Optional[TextAlign]: - return self.__text_align - - @text_align.setter - def text_align(self, value: Optional[TextAlign]): - self.__text_align = value - self._set_attr( - "textAlign", value.value if isinstance(value, TextAlign) else value - ) - - # max_lines - @property - def max_lines(self) -> Optional[int]: - return self._get_attr("maxLines") - - @max_lines.setter - def max_lines(self, value: Optional[int]): - self._set_attr("maxLines", value) - - # max_width - @property - def max_width(self) -> OptionalNumber: - return self._get_attr("maxWidth") - - @max_width.setter - def max_width(self, value: OptionalNumber): - self._set_attr("maxWidth", value) - - # ellipsis - @property - def ellipsis(self) -> Optional[str]: - return self._get_attr("ellipsis") - - @ellipsis.setter - def ellipsis(self, value: Optional[str]): - self._set_attr("ellipsis", value) - - # rotate - @property - def rotate(self) -> OptionalNumber: - return self._get_attr("rotate") - - @rotate.setter - def rotate(self, value: OptionalNumber): - self._set_attr("rotate", value) diff --git a/sdk/python/packages/flet/src/flet/core/card.py b/sdk/python/packages/flet/src/flet/core/card.py deleted file mode 100644 index b317c5eb2..000000000 --- a/sdk/python/packages/flet/src/flet/core/card.py +++ /dev/null @@ -1,285 +0,0 @@ -from enum import Enum -from typing import Any, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.buttons import OutlinedBorder -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ClipBehavior, - ColorEnums, - ColorValue, - MarginValue, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class CardVariant(Enum): - ELEVATED = "elevated" - FILLED = "filled" - OUTLINED = "outlined" - - -class Card(ConstrainedControl, AdaptiveControl): - """ - A material design card: a panel with slightly rounded corners and an elevation shadow. - - Example: - ``` - import flet as ft - - def main(page): - page.title = "Card Example" - page.add( - ft.Card( - content=ft.Container( - content=ft.Column( - [ - ft.ListTile( - leading=ft.Icon(ft.icons.ALBUM), - title=ft.Text("The Enchanted Nightingale"), - subtitle=ft.Text( - "Music by Julie Gable. Lyrics by Sidney Stein." - ), - ), - ft.Row( - [ft.TextButton("Buy tickets"), ft.TextButton("Listen")], - alignment=ft.MainAxisAlignment.END, - ), - ] - ), - width=400, - padding=10, - ) - ) - ) - - ft.app(target=main) - - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/card - """ - - def __init__( - self, - content: Optional[Control] = None, - margin: Optional[MarginValue] = None, - elevation: OptionalNumber = None, - color: Optional[ColorValue] = None, - shadow_color: Optional[ColorValue] = None, - surface_tint_color: Optional[ColorValue] = None, - shape: Optional[OutlinedBorder] = None, - clip_behavior: Optional[ClipBehavior] = None, - is_semantic_container: Optional[bool] = None, - show_border_on_foreground: Optional[bool] = None, - variant: Optional[CardVariant] = None, - # - # ConstrainedControl and AdaptiveControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - key: Optional[str] = None, - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.content = content - self.margin = margin - self.elevation = elevation - self.color = color - self.shadow_color = shadow_color - self.surface_tint_color = surface_tint_color - self.shape = shape - self.clip_behavior = clip_behavior - self.is_semantic_container = is_semantic_container - self.show_border_on_foreground = show_border_on_foreground - self.variant = variant - - def _get_control_name(self): - return "card" - - def before_update(self): - super().before_update() - self._set_attr_json("margin", self.__margin) - self._set_attr_json("shape", self.__shape) - - def _get_children(self): - children = [] - if self.__content is not None: - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - return children - - # margin - @property - def margin(self) -> Optional[MarginValue]: - return self.__margin - - @margin.setter - def margin(self, value: Optional[MarginValue]): - self.__margin = value - - # elevation - @property - def elevation(self) -> OptionalNumber: - return self._get_attr("elevation") - - @elevation.setter - def elevation(self, value: OptionalNumber): - self._set_attr("elevation", value) - - # color - @property - def color(self): - return self.__color - - @color.setter - def color(self, value): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # shadow_color - @property - def shadow_color(self): - return self.__shadow_color - - @shadow_color.setter - def shadow_color(self, value): - self.__shadow_color = value - self._set_enum_attr("shadowColor", value, ColorEnums) - - # surface_tint_color - @property - def surface_tint_color(self): - return self.__surface_tint_color - - @surface_tint_color.setter - def surface_tint_color(self, value): - self.__surface_tint_color = value - self._set_enum_attr("surfaceTintColor", value, ColorEnums) - - # shape - @property - def shape(self) -> Optional[OutlinedBorder]: - return self.__shape - - @shape.setter - def shape(self, value: Optional[OutlinedBorder]): - self.__shape = value - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # is_semantic_container - @property - def is_semantic_container(self) -> bool: - return self._get_attr("isSemanticContainer", data_type="bool", def_value=True) - - @is_semantic_container.setter - def is_semantic_container(self, value): - self._set_attr("isSemanticContainer", value) - - # show_border_on_foreground - @property - def show_border_on_foreground(self) -> bool: - return self._get_attr( - "showBorderOnForeground", data_type="bool", def_value=True - ) - - @show_border_on_foreground.setter - def show_border_on_foreground(self, value): - self._set_attr("showBorderOnForeground", value) - - # variant - @property - def variant(self) -> Optional[CardVariant]: - return self.__variant - - @variant.setter - def variant(self, value: Optional[CardVariant]): - self.__variant = value - self._set_enum_attr("variant", value, CardVariant) diff --git a/sdk/python/packages/flet/src/flet/core/charts/bar_chart.py b/sdk/python/packages/flet/src/flet/core/charts/bar_chart.py deleted file mode 100644 index e4953903a..000000000 --- a/sdk/python/packages/flet/src/flet/core/charts/bar_chart.py +++ /dev/null @@ -1,435 +0,0 @@ -import json -from enum import Enum -from typing import Any, List, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.border import Border, BorderSide -from flet.core.charts.bar_chart_group import BarChartGroup -from flet.core.charts.chart_axis import ChartAxis -from flet.core.charts.chart_grid_lines import ChartGridLines -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class TooltipDirection(Enum): - AUTO = "auto" - TOP = "top" - BOTTOM = "bottom" - - -class BarChart(ConstrainedControl): - def __init__( - self, - bar_groups: Optional[List[BarChartGroup]] = None, - groups_space: OptionalNumber = None, - animate: Optional[AnimationValue] = None, - interactive: Optional[bool] = None, - bgcolor: Optional[ColorValue] = None, - tooltip_bgcolor: Optional[ColorValue] = None, - border: Optional[Border] = None, - horizontal_grid_lines: Optional[ChartGridLines] = None, - vertical_grid_lines: Optional[ChartGridLines] = None, - left_axis: Optional[ChartAxis] = None, - top_axis: Optional[ChartAxis] = None, - right_axis: Optional[ChartAxis] = None, - bottom_axis: Optional[ChartAxis] = None, - baseline_y: OptionalNumber = None, - min_y: OptionalNumber = None, - max_y: OptionalNumber = None, - tooltip_rounded_radius: OptionalNumber = None, - tooltip_margin: OptionalNumber = None, - tooltip_padding: Optional[PaddingValue] = None, - tooltip_max_content_width: OptionalNumber = None, - tooltip_rotate_angle: OptionalNumber = None, - tooltip_tooltip_horizontal_offset: OptionalNumber = None, - tooltip_tooltip_border_side: Optional[BorderSide] = None, - tooltip_fit_inside_horizontally: Optional[bool] = None, - tooltip_fit_inside_vertically: Optional[bool] = None, - tooltip_direction: Optional[TooltipDirection] = None, - on_chart_event: OptionalEventCallable["BarChartEvent"] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.__on_chart_event = EventHandler(lambda e: BarChartEvent(e)) - self._add_event_handler("chart_event", self.__on_chart_event.get_handler()) - - self.bar_groups = bar_groups - self.groups_space = groups_space - self.animate = animate - self.interactive = interactive - self.bgcolor = bgcolor - self.tooltip_bgcolor = tooltip_bgcolor - self.border = border - self.horizontal_grid_lines = horizontal_grid_lines - self.vertical_grid_lines = vertical_grid_lines - self.left_axis = left_axis - self.top_axis = top_axis - self.right_axis = right_axis - self.bottom_axis = bottom_axis - self.baseline_y = baseline_y - self.min_y = min_y - self.max_y = max_y - self.on_chart_event = on_chart_event - self.tooltip_rounded_radius = tooltip_rounded_radius - self.tooltip_margin = tooltip_margin - self.tooltip_padding = tooltip_padding - self.tooltip_direction = tooltip_direction - self.tooltip_max_content_width = tooltip_max_content_width - self.tooltip_rotate_angle = tooltip_rotate_angle - self.tooltip_horizontal_offset = tooltip_tooltip_horizontal_offset - self.tooltip_border_side = tooltip_tooltip_border_side - self.tooltip_fit_inside_horizontally = tooltip_fit_inside_horizontally - self.tooltip_fit_inside_vertically = tooltip_fit_inside_vertically - - def _get_control_name(self): - return "barchart" - - def before_update(self): - super().before_update() - self._set_attr_json("horizontalGridLines", self.__horizontal_grid_lines) - self._set_attr_json("verticalGridLines", self.__vertical_grid_lines) - self._set_attr_json("animate", self.__animate) - self._set_attr_json("border", self.__border) - self._set_attr_json("tooltipBorderSide", self.__tooltip_border_side) - self._set_attr_json("tooltipPadding", self.__tooltip_padding) - - def _get_children(self): - children = [] - for ds in self.__bar_groups: - children.append(ds) - if self.__left_axis: - self.__left_axis._set_attr_internal("n", "l") - children.append(self.__left_axis) - if self.__top_axis: - self.__top_axis._set_attr_internal("n", "t") - children.append(self.__top_axis) - if self.__right_axis: - self.__right_axis._set_attr_internal("n", "r") - children.append(self.__right_axis) - if self.__bottom_axis: - self.__bottom_axis._set_attr_internal("n", "b") - children.append(self.__bottom_axis) - return children - - # bar_groups - @property - def bar_groups(self): - return self.__bar_groups - - @bar_groups.setter - def bar_groups(self, value): - self.__bar_groups = value if value is not None else [] - - # groups_space - @property - def groups_space(self) -> OptionalNumber: - return self._get_attr("groupsSpace", data_type="float") - - @groups_space.setter - def groups_space(self, value: OptionalNumber): - self._set_attr("groupsSpace", value) - - # animate - @property - def animate(self) -> AnimationValue: - return self.__animate - - @animate.setter - def animate(self, value: AnimationValue): - self.__animate = value - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # interactive - @property - def interactive(self) -> bool: - return self._get_attr("interactive", data_type="bool", def_value=True) - - @interactive.setter - def interactive(self, value: Optional[bool]): - self._set_attr("interactive", value) - - # tooltip_bgcolor - @property - def tooltip_bgcolor(self) -> Optional[str]: - return self.__tooltip_bgcolor - - @tooltip_bgcolor.setter - def tooltip_bgcolor(self, value: Optional[str]): - self.__tooltip_bgcolor = value - self._set_enum_attr("tooltipBgcolor", value, ColorEnums) - - # border - @property - def border(self) -> Optional[Border]: - return self.__border - - @border.setter - def border(self, value: Optional[Border]): - self.__border = value - - # horizontal_grid_lines - @property - def horizontal_grid_lines(self) -> Optional[ChartGridLines]: - return self.__horizontal_grid_lines - - @horizontal_grid_lines.setter - def horizontal_grid_lines(self, value: Optional[ChartGridLines]): - self.__horizontal_grid_lines = value - - # vertical_grid_lines - @property - def vertical_grid_lines(self) -> Optional[ChartGridLines]: - return self.__vertical_grid_lines - - @vertical_grid_lines.setter - def vertical_grid_lines(self, value: Optional[ChartGridLines]): - self.__vertical_grid_lines = value - - # left_axis - @property - def left_axis(self) -> Optional[ChartAxis]: - return self.__left_axis - - @left_axis.setter - def left_axis(self, value: Optional[ChartAxis]): - self.__left_axis = value - - # top_axis - @property - def top_axis(self) -> Optional[ChartAxis]: - return self.__top_axis - - @top_axis.setter - def top_axis(self, value: Optional[ChartAxis]): - self.__top_axis = value - - # right_axis - @property - def right_axis(self) -> Optional[ChartAxis]: - return self.__right_axis - - @right_axis.setter - def right_axis(self, value: Optional[ChartAxis]): - self.__right_axis = value - - # bottom_axis - @property - def bottom_axis(self) -> Optional[ChartAxis]: - return self.__bottom_axis - - @bottom_axis.setter - def bottom_axis(self, value: Optional[ChartAxis]): - self.__bottom_axis = value - - # baseline_y - @property - def baseline_y(self) -> OptionalNumber: - return self._get_attr("baseliney", data_type="float") - - @baseline_y.setter - def baseline_y(self, value: OptionalNumber): - self._set_attr("baseliney", value) - - # min_y - @property - def min_y(self) -> OptionalNumber: - return self._get_attr("miny", data_type="float") - - @min_y.setter - def min_y(self, value: OptionalNumber): - self._set_attr("miny", value) - - # max_y - @property - def max_y(self) -> OptionalNumber: - return self._get_attr("maxy", data_type="float") - - @max_y.setter - def max_y(self, value: OptionalNumber): - self._set_attr("maxy", value) - - # tooltip_rounded_radius - @property - def tooltip_rounded_radius(self) -> OptionalNumber: - return self._get_attr("tooltipRoundedRadius", data_type="float") - - @tooltip_rounded_radius.setter - def tooltip_rounded_radius(self, value: OptionalNumber): - self._set_attr("tooltipRoundedRadius", value) - - # tooltip_margin - @property - def tooltip_margin(self) -> OptionalNumber: - return self._get_attr("tooltipMargin", data_type="float") - - @tooltip_margin.setter - def tooltip_margin(self, value: OptionalNumber): - self._set_attr("tooltipMargin", value) - - # tooltip_padding - @property - def tooltip_padding(self) -> Optional[PaddingValue]: - return self.__tooltip_padding - - @tooltip_padding.setter - def tooltip_padding(self, value: Optional[PaddingValue]): - self.__tooltip_padding = value - - # tooltip_max_content_width - @property - def tooltip_max_content_width(self) -> OptionalNumber: - return self._get_attr("tooltipMaxContentWidth", data_type="float") - - @tooltip_max_content_width.setter - def tooltip_max_content_width(self, value: OptionalNumber): - self._set_attr("tooltipMaxContentWidth", value) - - # tooltip_rotate_angle - @property - def tooltip_rotate_angle(self) -> OptionalNumber: - return self._get_attr("tooltipRotateAngle", data_type="float") - - @tooltip_rotate_angle.setter - def tooltip_rotate_angle(self, value: OptionalNumber): - self._set_attr("tooltipRotateAngle", value) - - # tooltip_fit_inside_vertically - @property - def tooltip_fit_inside_vertically(self) -> Optional[bool]: - return self._get_attr("tooltipFitInsideVertically", data_type="bool") - - @tooltip_fit_inside_vertically.setter - def tooltip_fit_inside_vertically(self, value: Optional[bool]): - self._set_attr("tooltipFitInsideVertically", value) - - # tooltip_fit_inside_horizontally - @property - def tooltip_fit_inside_horizontally(self) -> Optional[bool]: - return self._get_attr("tooltipFitInsideHorizontally", data_type="bool") - - @tooltip_fit_inside_horizontally.setter - def tooltip_fit_inside_horizontally(self, value: Optional[bool]): - self._set_attr("tooltipFitInsideHorizontally", value) - - # tooltip_border_side - @property - def tooltip_border_side(self) -> Optional[BorderSide]: - return self.__tooltip_border_side - - @tooltip_border_side.setter - def tooltip_border_side(self, value: Optional[BorderSide]): - self.__tooltip_border_side = value - - # tooltip_direction - @property - def tooltip_direction(self) -> Optional[TooltipDirection]: - return self.__tooltip_direction - - @tooltip_direction.setter - def tooltip_direction(self, value: Optional[TooltipDirection]): - self.__tooltip_direction = value - self._set_enum_attr("tooltipDirection", value, TooltipDirection) - - # on_chart_event - @property - def on_chart_event(self) -> OptionalEventCallable["BarChartEvent"]: - return self.__on_chart_event.handler - - @on_chart_event.setter - def on_chart_event(self, handler: OptionalEventCallable["BarChartEvent"]): - self.__on_chart_event.handler = handler - self._set_attr("onChartEvent", True if handler is not None else None) - - -class BarChartEvent(ControlEvent): - def __init__(self, e: ControlEvent) -> None: - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.type: str = d.get("type") - self.group_index: int = d.get("group_index") - self.rod_index: int = d.get("rod_index") - self.stack_item_index: int = d.get("stack_item_index") diff --git a/sdk/python/packages/flet/src/flet/core/charts/bar_chart_group.py b/sdk/python/packages/flet/src/flet/core/charts/bar_chart_group.py deleted file mode 100644 index d454b00bc..000000000 --- a/sdk/python/packages/flet/src/flet/core/charts/bar_chart_group.py +++ /dev/null @@ -1,80 +0,0 @@ -from typing import Any, List, Optional - -from flet.core.charts.bar_chart_rod import BarChartRod -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref - - -class BarChartGroup(Control): - def __init__( - self, - x: Optional[int] = None, - bar_rods: Optional[List[BarChartRod]] = None, - group_vertically: Optional[bool] = None, - bars_space: OptionalNumber = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - self.x = x - self.bar_rods = bar_rods - self.group_vertically = group_vertically - self.bars_space = bars_space - - def _get_control_name(self): - return "group" - - def before_update(self): - super().before_update() - - def _get_children(self): - return self.__bar_rods - - # bar_rods - @property - def bar_rods(self): - return self.__bar_rods - - @bar_rods.setter - def bar_rods(self, value): - self.__bar_rods = value if value is not None else [] - - # x - @property - def x(self) -> Optional[int]: - return self._get_attr("x", data_type="int") - - @x.setter - def x(self, value: Optional[int]): - self._set_attr("x", value) - - # group_vertically - @property - def group_vertically(self) -> bool: - return self._get_attr("groupVertically", data_type="bool", def_value=False) - - @group_vertically.setter - def group_vertically(self, value: Optional[bool]): - self._set_attr("groupVertically", value) - - # bars_space - @property - def bars_space(self) -> OptionalNumber: - return self._get_attr("barsSpace", data_type="float") - - @bars_space.setter - def bars_space(self, value: OptionalNumber): - self._set_attr("barsSpace", value) diff --git a/sdk/python/packages/flet/src/flet/core/charts/bar_chart_rod.py b/sdk/python/packages/flet/src/flet/core/charts/bar_chart_rod.py deleted file mode 100644 index d2c7d16a5..000000000 --- a/sdk/python/packages/flet/src/flet/core/charts/bar_chart_rod.py +++ /dev/null @@ -1,239 +0,0 @@ -from typing import Any, List, Optional - -from flet.core.badge import BadgeValue -from flet.core.border import BorderSide -from flet.core.charts.bar_chart_rod_stack_item import BarChartRodStackItem -from flet.core.control import Control, OptionalNumber -from flet.core.gradients import Gradient -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.types import BorderRadiusValue, ColorEnums, ColorValue, TextAlign - - -class BarChartRod(Control): - def __init__( - self, - rod_stack_items: Optional[List[BarChartRodStackItem]] = None, - from_y: OptionalNumber = None, - to_y: OptionalNumber = None, - width: OptionalNumber = None, - color: Optional[ColorValue] = None, - gradient: Optional[Gradient] = None, - border_radius: Optional[BorderRadiusValue] = None, - border_side: Optional[BorderSide] = None, - bg_from_y: OptionalNumber = None, - bg_to_y: OptionalNumber = None, - bg_color: Optional[ColorValue] = None, - bg_gradient: Optional[Gradient] = None, - selected: Optional[bool] = None, - show_tooltip: Optional[bool] = None, - tooltip: Optional[str] = None, - tooltip_style: Optional[TextStyle] = None, - tooltip_align: Optional[TextAlign] = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - badge: Optional[BadgeValue] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - badge=badge, - ) - - self.rod_stack_items = rod_stack_items - self.from_y = from_y - self.to_y = to_y - self.width = width - self.color = color - self.gradient = gradient - self.border_side = border_side - self.border_radius = border_radius - self.bg_from_y = bg_from_y - self.bg_to_y = bg_to_y - self.bg_color = bg_color - self.bg_gradient = bg_gradient - self.selected = selected - self.show_tooltip = show_tooltip - self.tooltip = tooltip - self.tooltip_align = tooltip_align - self.tooltip_style = tooltip_style - - def _get_control_name(self): - return "bar_chart_rod" - - def before_update(self): - super().before_update() - self._set_attr_json("gradient", self.__gradient) - self._set_attr_json("borderSide", self.__border_side) - self._set_attr_json("borderRadius", self.__border_radius) - self._set_attr_json("bgGradient", self.__bg_gradient) - - def _get_children(self): - return self.__rod_stack_items - - # rod_stack_items - @property - def rod_stack_items(self): - return self.__rod_stack_items - - @rod_stack_items.setter - def rod_stack_items(self, value): - self.__rod_stack_items = value if value is not None else [] - - # from_y - @property - def from_y(self) -> OptionalNumber: - return self._get_attr("fromY", data_type="float") - - @from_y.setter - def from_y(self, value: OptionalNumber): - self._set_attr("fromY", value) - - # to_y - @property - def to_y(self) -> OptionalNumber: - return self._get_attr("toY", data_type="float") - - @to_y.setter - def to_y(self, value: OptionalNumber): - self._set_attr("toY", value) - - # width - @property - def width(self) -> OptionalNumber: - return self._get_attr("width", data_type="float") - - @width.setter - def width(self, value: OptionalNumber): - self._set_attr("width", value) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # border_side - @property - def border_side(self) -> Optional[BorderSide]: - return self.__border_side - - @border_side.setter - def border_side(self, value: Optional[BorderSide]): - self.__border_side = value - - # border_radius - @property - def border_radius(self) -> Optional[BorderRadiusValue]: - return self.__border_radius - - @border_radius.setter - def border_radius(self, value: Optional[BorderRadiusValue]): - self.__border_radius = value - - # gradient - @property - def gradient(self) -> Optional[Gradient]: - return self.__gradient - - @gradient.setter - def gradient(self, value: Optional[Gradient]): - self.__gradient = value - - # bg_from_y - @property - def bg_from_y(self) -> OptionalNumber: - return self._get_attr("bgFromY", data_type="float") - - @bg_from_y.setter - def bg_from_y(self, value: OptionalNumber): - self._set_attr("bgFromY", value) - - # bg_to_y - @property - def bg_to_y(self) -> OptionalNumber: - return self._get_attr("bgToY", data_type="float") - - @bg_to_y.setter - def bg_to_y(self, value: OptionalNumber): - self._set_attr("bgToY", value) - - # bg_color - @property - def bg_color(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bg_color.setter - def bg_color(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgColor", value, ColorEnums) - - # bg_gradient - @property - def bg_gradient(self) -> Optional[Gradient]: - return self.__bg_gradient - - @bg_gradient.setter - def bg_gradient(self, value: Optional[Gradient]): - self.__bg_gradient = value - - # selected - @property - def selected(self) -> bool: - return self._get_attr("selected", data_type="bool", def_value=False) - - @selected.setter - def selected(self, value: Optional[bool]): - self._set_attr("selected", value) - - # show_tooltip - @property - def show_tooltip(self) -> bool: - return self._get_attr("showTooltip", data_type="bool", def_value=True) - - @show_tooltip.setter - def show_tooltip(self, value: Optional[bool]): - self._set_attr("showTooltip", value) - - # tooltip - @property - def tooltip(self) -> Optional[str]: - return self._get_attr("tooltip") - - @tooltip.setter - def tooltip(self, value: Optional[str]): - self._set_attr("tooltip", value) - - # tooltip_align - @property - def tooltip_align(self) -> Optional[TextAlign]: - return self.__tooltip_align - - @tooltip_align.setter - def tooltip_align(self, value: Optional[TextAlign]): - self.__tooltip_align = value - self._set_attr( - "tooltipAlign", value.value if isinstance(value, TextAlign) else value - ) - - # tooltip_style - @property - def tooltip_style(self): - return self.__tooltip_style - - @tooltip_style.setter - def tooltip_style(self, value: Optional[TextStyle]): - self.__tooltip_style = value diff --git a/sdk/python/packages/flet/src/flet/core/charts/bar_chart_rod_stack_item.py b/sdk/python/packages/flet/src/flet/core/charts/bar_chart_rod_stack_item.py deleted file mode 100644 index 563ef5113..000000000 --- a/sdk/python/packages/flet/src/flet/core/charts/bar_chart_rod_stack_item.py +++ /dev/null @@ -1,80 +0,0 @@ -from typing import Any, Optional - -from flet.core.border import BorderSide -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.types import ColorEnums, ColorValue - - -class BarChartRodStackItem(Control): - def __init__( - self, - from_y: OptionalNumber = None, - to_y: OptionalNumber = None, - color: Optional[ColorValue] = None, - border_side: Optional[BorderSide] = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - self.from_y = from_y - self.to_y = to_y - self.color = color - self.border_side = border_side - - def _get_control_name(self): - return "stack_item" - - def before_update(self): - super().before_update() - self._set_attr_json("borderSide", self.__border_side) - - # from_y - @property - def from_y(self) -> OptionalNumber: - return self._get_attr("fromY", data_type="float") - - @from_y.setter - def from_y(self, value: OptionalNumber): - self._set_attr("fromY", value) - - # to_y - @property - def to_y(self) -> OptionalNumber: - return self._get_attr("toY", data_type="float") - - @to_y.setter - def to_y(self, value: OptionalNumber): - self._set_attr("toY", value) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # border_side - @property - def border_side(self) -> Optional[BorderSide]: - return self.__border_side - - @border_side.setter - def border_side(self, value: Optional[BorderSide]): - self.__border_side = value diff --git a/sdk/python/packages/flet/src/flet/core/charts/chart_axis.py b/sdk/python/packages/flet/src/flet/core/charts/chart_axis.py deleted file mode 100644 index c55f47ac6..000000000 --- a/sdk/python/packages/flet/src/flet/core/charts/chart_axis.py +++ /dev/null @@ -1,106 +0,0 @@ -from typing import Any, List, Optional - -from flet.core.charts.chart_axis_label import ChartAxisLabel -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref - - -class ChartAxis(Control): - def __init__( - self, - title: Optional[Control] = None, - title_size: OptionalNumber = None, - show_labels: Optional[bool] = None, - labels: Optional[List[ChartAxisLabel]] = None, - labels_interval: OptionalNumber = None, - labels_size: OptionalNumber = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - self.title = title - self.title_size = title_size - self.show_labels = show_labels - self.labels = labels - self.labels_interval = labels_interval - self.labels_size = labels_size - - def _get_control_name(self): - return "axis" - - def _get_children(self): - children = [] - for label in self.__labels: - label._set_attr_internal("n", "l") - children.append(label) - if self.__title: - self.__title._set_attr_internal("n", "t") - children.append(self.__title) - return children - - # title - @property - def title(self) -> Optional[Control]: - return self.__title - - @title.setter - def title(self, value: Optional[Control]): - self.__title = value - - # title_size - @property - def title_size(self) -> OptionalNumber: - return self._get_attr("titleSize", data_type="float") - - @title_size.setter - def title_size(self, value: OptionalNumber): - self._set_attr("titleSize", value) - - # show_labels - @property - def show_labels(self) -> bool: - return self._get_attr("showLabels", data_type="bool", def_value=True) - - @show_labels.setter - def show_labels(self, value: Optional[bool]): - self._set_attr("showLabels", value) - - # labels - @property - def labels(self): - return self.__labels - - @labels.setter - def labels(self, value): - self.__labels = value if value is not None else [] - - # labels_interval - @property - def labels_interval(self) -> float: - return self._get_attr("labelsInterval", data_type="float", def_value=1.0) - - @labels_interval.setter - def labels_interval(self, value: OptionalNumber): - self._set_attr("labelsInterval", value) - - # labels_size - @property - def labels_size(self) -> OptionalNumber: - return self._get_attr("labelsSize", data_type="float") - - @labels_size.setter - def labels_size(self, value: OptionalNumber): - self._set_attr("labelsSize", value) diff --git a/sdk/python/packages/flet/src/flet/core/charts/chart_axis_label.py b/sdk/python/packages/flet/src/flet/core/charts/chart_axis_label.py deleted file mode 100644 index 05fbd10b9..000000000 --- a/sdk/python/packages/flet/src/flet/core/charts/chart_axis_label.py +++ /dev/null @@ -1,57 +0,0 @@ -from typing import Any, Optional - -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref - - -class ChartAxisLabel(Control): - def __init__( - self, - value: OptionalNumber = None, - label: Optional[Control] = None, - # - # Specific - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - self.value = value - self.label = label - - def _get_control_name(self): - return "l" - - def _get_children(self): - children = [] - if self.__label: - children.append(self.__label) - return children - - # value - @property - def value(self) -> float: - return self._get_attr("value", data_type="float", def_value=1.0) - - @value.setter - def value(self, value: OptionalNumber): - self._set_attr("value", value) - - # label - @property - def label(self) -> Optional[Control]: - return self.__label - - @label.setter - def label(self, value: Optional[Control]): - self.__label = value diff --git a/sdk/python/packages/flet/src/flet/core/charts/chart_grid_lines.py b/sdk/python/packages/flet/src/flet/core/charts/chart_grid_lines.py deleted file mode 100644 index 1d9e9d636..000000000 --- a/sdk/python/packages/flet/src/flet/core/charts/chart_grid_lines.py +++ /dev/null @@ -1,12 +0,0 @@ -from dataclasses import dataclass -from typing import List, Optional - -from flet.core.types import ColorValue - - -@dataclass -class ChartGridLines: - interval: Optional[float] = None - color: Optional[ColorValue] = None - width: Optional[float] = None - dash_pattern: Optional[List[int]] = None diff --git a/sdk/python/packages/flet/src/flet/core/charts/chart_point_line.py b/sdk/python/packages/flet/src/flet/core/charts/chart_point_line.py deleted file mode 100644 index b1b942b2d..000000000 --- a/sdk/python/packages/flet/src/flet/core/charts/chart_point_line.py +++ /dev/null @@ -1,11 +0,0 @@ -from dataclasses import dataclass, field -from typing import List, Optional - -from flet.core.types import ColorValue - - -@dataclass -class ChartPointLine: - color: Optional[ColorValue] = field(default=None) - width: Optional[float] = field(default=None) - dash_pattern: Optional[List[int]] = field(default=None) diff --git a/sdk/python/packages/flet/src/flet/core/charts/chart_point_shape.py b/sdk/python/packages/flet/src/flet/core/charts/chart_point_shape.py deleted file mode 100644 index 07130a5d2..000000000 --- a/sdk/python/packages/flet/src/flet/core/charts/chart_point_shape.py +++ /dev/null @@ -1,41 +0,0 @@ -from dataclasses import dataclass -from typing import Optional - -from flet.core.types import ColorValue, OptionalNumber - - -@dataclass -class ChartPointShape: - pass - - -@dataclass -class ChartCirclePoint(ChartPointShape): - color: Optional[ColorValue] = None - radius: OptionalNumber = None - stroke_color: Optional[ColorValue] = None - stroke_width: OptionalNumber = None - - def __post_init__(self): - self.type = "circle" - - -@dataclass -class ChartSquarePoint(ChartPointShape): - color: Optional[ColorValue] = None - size: OptionalNumber = None - stroke_color: Optional[ColorValue] = None - stroke_width: OptionalNumber = None - - def __post_init__(self): - self.type = "square" - - -@dataclass -class ChartCrossPoint(ChartPointShape): - color: Optional[ColorValue] = None - size: OptionalNumber = None - width: OptionalNumber = None - - def __post_init__(self): - self.type = "cross" diff --git a/sdk/python/packages/flet/src/flet/core/charts/line_chart.py b/sdk/python/packages/flet/src/flet/core/charts/line_chart.py deleted file mode 100644 index 74b3ae54b..000000000 --- a/sdk/python/packages/flet/src/flet/core/charts/line_chart.py +++ /dev/null @@ -1,485 +0,0 @@ -import json -from typing import Any, List, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.border import Border, BorderSide -from flet.core.charts.chart_axis import ChartAxis -from flet.core.charts.chart_grid_lines import ChartGridLines -from flet.core.charts.line_chart_data import LineChartData -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class LineChart(ConstrainedControl): - def __init__( - self, - data_series: Optional[List[LineChartData]] = None, - animate: Optional[AnimationValue] = None, - interactive: Optional[bool] = None, - point_line_start: OptionalNumber = None, - point_line_end: OptionalNumber = None, - bgcolor: Optional[ColorValue] = None, - tooltip_bgcolor: Optional[ColorValue] = None, - border: Optional[Border] = None, - horizontal_grid_lines: Optional[ChartGridLines] = None, - vertical_grid_lines: Optional[ChartGridLines] = None, - left_axis: Optional[ChartAxis] = None, - top_axis: Optional[ChartAxis] = None, - right_axis: Optional[ChartAxis] = None, - bottom_axis: Optional[ChartAxis] = None, - baseline_x: OptionalNumber = None, - min_x: OptionalNumber = None, - max_x: OptionalNumber = None, - baseline_y: OptionalNumber = None, - min_y: OptionalNumber = None, - max_y: OptionalNumber = None, - tooltip_rounded_radius: OptionalNumber = None, - tooltip_margin: OptionalNumber = None, - tooltip_padding: Optional[PaddingValue] = None, - tooltip_max_content_width: OptionalNumber = None, - tooltip_rotate_angle: OptionalNumber = None, - tooltip_tooltip_horizontal_offset: OptionalNumber = None, - tooltip_tooltip_border_side: Optional[BorderSide] = None, - tooltip_fit_inside_horizontally: Optional[bool] = None, - tooltip_fit_inside_vertically: Optional[bool] = None, - tooltip_show_on_top_of_chart_box_area: Optional[bool] = None, - on_chart_event: OptionalEventCallable["LineChartEvent"] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.__on_chart_event = EventHandler(lambda e: LineChartEvent(e)) - self._add_event_handler("chart_event", self.__on_chart_event.get_handler()) - - self.data_series = data_series - self.animate = animate - self.interactive = interactive - self.point_line_start = point_line_start - self.point_line_end = point_line_end - self.bgcolor = bgcolor - self.tooltip_bgcolor = tooltip_bgcolor - self.border = border - self.horizontal_grid_lines = horizontal_grid_lines - self.vertical_grid_lines = vertical_grid_lines - self.left_axis = left_axis - self.top_axis = top_axis - self.right_axis = right_axis - self.bottom_axis = bottom_axis - self.baseline_x = baseline_x - self.baseline_y = baseline_y - self.min_x = min_x - self.max_x = max_x - self.min_y = min_y - self.max_y = max_y - self.on_chart_event = on_chart_event - self.tooltip_rounded_radius = tooltip_rounded_radius - self.tooltip_margin = tooltip_margin - self.tooltip_padding = tooltip_padding - self.tooltip_max_content_width = tooltip_max_content_width - self.tooltip_rotate_angle = tooltip_rotate_angle - self.tooltip_horizontal_offset = tooltip_tooltip_horizontal_offset - self.tooltip_border_side = tooltip_tooltip_border_side - self.tooltip_fit_inside_horizontally = tooltip_fit_inside_horizontally - self.tooltip_fit_inside_vertically = tooltip_fit_inside_vertically - self.tooltip_show_on_top_of_chart_box_area = ( - tooltip_show_on_top_of_chart_box_area - ) - - def _get_control_name(self): - return "linechart" - - def before_update(self): - super().before_update() - self._set_attr_json("horizontalGridLines", self.__horizontal_grid_lines) - self._set_attr_json("verticalGridLines", self.__vertical_grid_lines) - self._set_attr_json("animate", self.__animate) - self._set_attr_json("border", self.__border) - self._set_attr_json("tooltipBorderSide", self.__tooltip_border_side) - self._set_attr_json("tooltipPadding", self.__tooltip_padding) - - def _get_children(self): - children = [] - for ds in self.__data_series: - children.append(ds) - if self.__left_axis: - self.__left_axis._set_attr_internal("n", "l") - children.append(self.__left_axis) - if self.__top_axis: - self.__top_axis._set_attr_internal("n", "t") - children.append(self.__top_axis) - if self.__right_axis: - self.__right_axis._set_attr_internal("n", "r") - children.append(self.__right_axis) - if self.__bottom_axis: - self.__bottom_axis._set_attr_internal("n", "b") - children.append(self.__bottom_axis) - return children - - # data_series - @property - def data_series(self): - return self.__data_series - - @data_series.setter - def data_series(self, value): - self.__data_series = value if value is not None else [] - - # animate - @property - def animate(self) -> AnimationValue: - return self.__animate - - @animate.setter - def animate(self, value: AnimationValue): - self.__animate = value - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # interactive - @property - def interactive(self) -> bool: - return self._get_attr("interactive", data_type="bool", def_value=True) - - @interactive.setter - def interactive(self, value: Optional[bool]): - self._set_attr("interactive", value) - - # point_line_start - @property - def point_line_start(self) -> OptionalNumber: - return self._get_attr("pointLineStart", data_type="float") - - @point_line_start.setter - def point_line_start(self, value: OptionalNumber): - self._set_attr("pointLineStart", value) - - # point_line_end - @property - def point_line_end(self) -> OptionalNumber: - return self._get_attr("pointLineEnd", data_type="float") - - @point_line_end.setter - def point_line_end(self, value: OptionalNumber): - self._set_attr("pointLineEnd", value) - - # tooltip_bgcolor - @property - def tooltip_bgcolor(self) -> Optional[str]: - return self.__tooltip_bgcolor - - @tooltip_bgcolor.setter - def tooltip_bgcolor(self, value: Optional[str]): - self.__tooltip_bgcolor = value - self._set_enum_attr("tooltipBgcolor", value, ColorEnums) - - # border - @property - def border(self) -> Optional[Border]: - return self.__border - - @border.setter - def border(self, value: Optional[Border]): - self.__border = value - - # horizontal_grid_lines - @property - def horizontal_grid_lines(self) -> Optional[ChartGridLines]: - return self.__horizontal_grid_lines - - @horizontal_grid_lines.setter - def horizontal_grid_lines(self, value: Optional[ChartGridLines]): - self.__horizontal_grid_lines = value - - # vertical_grid_lines - @property - def vertical_grid_lines(self) -> Optional[ChartGridLines]: - return self.__vertical_grid_lines - - @vertical_grid_lines.setter - def vertical_grid_lines(self, value: Optional[ChartGridLines]): - self.__vertical_grid_lines = value - - # left_axis - @property - def left_axis(self) -> Optional[ChartAxis]: - return self.__left_axis - - @left_axis.setter - def left_axis(self, value: Optional[ChartAxis]): - self.__left_axis = value - - # top_axis - @property - def top_axis(self) -> Optional[ChartAxis]: - return self.__top_axis - - @top_axis.setter - def top_axis(self, value: Optional[ChartAxis]): - self.__top_axis = value - - # right_axis - @property - def right_axis(self) -> Optional[ChartAxis]: - return self.__right_axis - - @right_axis.setter - def right_axis(self, value: Optional[ChartAxis]): - self.__right_axis = value - - # bottom_axis - @property - def bottom_axis(self) -> Optional[ChartAxis]: - return self.__bottom_axis - - @bottom_axis.setter - def bottom_axis(self, value: Optional[ChartAxis]): - self.__bottom_axis = value - - # baseline_x - @property - def baseline_x(self) -> OptionalNumber: - return self._get_attr("baselinex", data_type="float") - - @baseline_x.setter - def baseline_x(self, value: OptionalNumber): - self._set_attr("baselineX", value) - - # baseline_y - @property - def baseline_y(self) -> OptionalNumber: - return self._get_attr("baselineY", data_type="float") - - @baseline_y.setter - def baseline_y(self, value: OptionalNumber): - self._set_attr("baselineY", value) - - # min_x - @property - def min_x(self) -> OptionalNumber: - return self._get_attr("minX", data_type="float") - - @min_x.setter - def min_x(self, value: OptionalNumber): - self._set_attr("minX", value) - - # max_x - @property - def max_x(self) -> OptionalNumber: - return self._get_attr("maxX", data_type="float") - - @max_x.setter - def max_x(self, value: OptionalNumber): - self._set_attr("maxX", value) - - # min_y - @property - def min_y(self) -> OptionalNumber: - return self._get_attr("minY", data_type="float") - - @min_y.setter - def min_y(self, value: OptionalNumber): - self._set_attr("minY", value) - - # max_y - @property - def max_y(self) -> OptionalNumber: - return self._get_attr("maxY", data_type="float") - - @max_y.setter - def max_y(self, value: OptionalNumber): - self._set_attr("maxY", value) - - # tooltip_rounded_radius - @property - def tooltip_rounded_radius(self) -> OptionalNumber: - return self._get_attr("tooltipRoundedRadius", data_type="float", def_value=4) - - @tooltip_rounded_radius.setter - def tooltip_rounded_radius(self, value: OptionalNumber): - self._set_attr("tooltipRoundedRadius", value) - - # tooltip_margin - @property - def tooltip_margin(self) -> OptionalNumber: - return self._get_attr("tooltipMargin", data_type="float", def_value=16) - - @tooltip_margin.setter - def tooltip_margin(self, value: OptionalNumber): - self._set_attr("tooltipMargin", value) - - # tooltip_padding - @property - def tooltip_padding(self) -> Optional[PaddingValue]: - return self.__tooltip_padding - - @tooltip_padding.setter - def tooltip_padding(self, value: Optional[PaddingValue]): - self.__tooltip_padding = value - - # tooltip_max_content_width - @property - def tooltip_max_content_width(self) -> OptionalNumber: - return self._get_attr( - "tooltipMaxContentWidth", data_type="float", def_value=120 - ) - - @tooltip_max_content_width.setter - def tooltip_max_content_width(self, value: OptionalNumber): - self._set_attr("tooltipMaxContentWidth", value) - - # tooltip_rotate_angle - @property - def tooltip_rotate_angle(self) -> OptionalNumber: - return self._get_attr("tooltipRotateAngle", data_type="float", def_value=0.0) - - @tooltip_rotate_angle.setter - def tooltip_rotate_angle(self, value: OptionalNumber): - self._set_attr("tooltipRotateAngle", value) - - # tooltip_fit_inside_vertically - @property - def tooltip_fit_inside_vertically(self) -> Optional[bool]: - return self._get_attr( - "tooltipFitInsideVertically", data_type="bool", def_value=False - ) - - @tooltip_fit_inside_vertically.setter - def tooltip_fit_inside_vertically(self, value: Optional[bool]): - self._set_attr("tooltipFitInsideVertically", value) - - # tooltip_fit_inside_horizontally - @property - def tooltip_fit_inside_horizontally(self) -> Optional[bool]: - return self._get_attr( - "tooltipFitInsideHorizontally", data_type="bool", def_value=False - ) - - @tooltip_fit_inside_horizontally.setter - def tooltip_fit_inside_horizontally(self, value: Optional[bool]): - self._set_attr("tooltipFitInsideHorizontally", value) - - # tooltip_show_on_top_of_chart_box_area - @property - def tooltip_show_on_top_of_chart_box_area(self) -> Optional[bool]: - return self._get_attr( - "tooltipShowOnTopOfChartBoxArea", data_type="bool", def_value=False - ) - - @tooltip_show_on_top_of_chart_box_area.setter - def tooltip_show_on_top_of_chart_box_area(self, value: Optional[bool]): - self._set_attr("tooltipShowOnTopOfChartBoxArea", value) - - # tooltip_border_side - @property - def tooltip_border_side(self) -> Optional[BorderSide]: - return self.__tooltip_border_side - - @tooltip_border_side.setter - def tooltip_border_side(self, value: Optional[BorderSide]): - self.__tooltip_border_side = value - - # on_chart_event - @property - def on_chart_event(self) -> OptionalEventCallable["LineChartEvent"]: - return self.__on_chart_event.handler - - @on_chart_event.setter - def on_chart_event(self, handler: OptionalEventCallable["LineChartEvent"]): - self.__on_chart_event.handler = handler - self._set_attr("onChartEvent", True if handler is not None else None) - - -class LineChartEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.type: str = d.get("type") - self.spots: List[LineChartEventSpot] = d.get("spots") - - -class LineChartEventSpot: - def __init__(self, bar_index, spot_index): - self.bar_index: int = bar_index - self.spot_index: int = spot_index diff --git a/sdk/python/packages/flet/src/flet/core/charts/line_chart_data.py b/sdk/python/packages/flet/src/flet/core/charts/line_chart_data.py deleted file mode 100644 index f13494f9e..000000000 --- a/sdk/python/packages/flet/src/flet/core/charts/line_chart_data.py +++ /dev/null @@ -1,289 +0,0 @@ -from typing import Any, List, Optional, Union - -from flet.core.box import BoxShadow -from flet.core.charts.chart_point_line import ChartPointLine -from flet.core.charts.chart_point_shape import ChartPointShape -from flet.core.charts.line_chart_data_point import LineChartDataPoint -from flet.core.control import Control, OptionalNumber -from flet.core.gradients import Gradient -from flet.core.ref import Ref -from flet.core.types import ColorEnums, ColorValue - - -class LineChartData(Control): - def __init__( - self, - data_points: Optional[List[LineChartDataPoint]] = None, - curved: Optional[bool] = None, - color: Optional[ColorValue] = None, - gradient: Optional[Gradient] = None, - stroke_width: OptionalNumber = None, - stroke_cap_round: Optional[bool] = None, - prevent_curve_over_shooting: Optional[bool] = None, - prevent_curve_over_shooting_threshold: OptionalNumber = None, - dash_pattern: Optional[List[int]] = None, - shadow: Optional[BoxShadow] = None, - above_line_bgcolor: Optional[ColorValue] = None, - above_line_gradient: Optional[Gradient] = None, - above_line_cutoff_y: OptionalNumber = None, - above_line: Optional[ChartPointLine] = None, - below_line_bgcolor: Optional[ColorValue] = None, - below_line_gradient: Optional[Gradient] = None, - below_line_cutoff_y: OptionalNumber = None, - below_line: Optional[ChartPointLine] = None, - selected_below_line: Union[None, bool, ChartPointLine] = None, - point: Union[None, bool, ChartPointShape] = None, - selected_point: Union[None, bool, ChartPointShape] = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - self.data_points = data_points - self.curved = curved - self.color = color - self.gradient = gradient - self.stroke_width = stroke_width - self.stroke_cap_round = stroke_cap_round - self.prevent_curve_over_shooting = prevent_curve_over_shooting - self.prevent_curve_over_shooting_threshold = ( - prevent_curve_over_shooting_threshold - ) - self.shadow = shadow - self.dash_pattern = dash_pattern - self.above_line_bgcolor = above_line_bgcolor - self.above_line_gradient = above_line_gradient - self.above_line_cutoff_y = above_line_cutoff_y - self.above_line = above_line - self.below_line_bgcolor = below_line_bgcolor - self.below_line_gradient = below_line_gradient - self.below_line_cutoff_y = below_line_cutoff_y - self.below_line = below_line - self.selected_below_line = selected_below_line - self.point = point - self.selected_point = selected_point - - def _get_control_name(self): - return "data" - - def before_update(self): - super().before_update() - self._set_attr_json("gradient", self.__gradient) - self._set_attr_json("shadow", self.__shadow) - self._set_attr_json("point", self.__point) - self._set_attr_json("selectedPoint", self.__selected_point) - self._set_attr_json("dashPattern", self.__dash_pattern) - self._set_attr_json("aboveLineGradient", self.__above_line_gradient) - self._set_attr_json("belowLineGradient", self.__below_line_gradient) - self._set_attr_json("aboveLine", self.__above_line) - self._set_attr_json("belowLine", self.__below_line) - self._set_attr_json("selectedBelowLine", self.__selected_below_line) - - def _get_children(self): - return self.__data_points - - # data_points - @property - def data_points(self): - return self.__data_points - - @data_points.setter - def data_points(self, value): - self.__data_points = value if value is not None else [] - - # stroke_width - @property - def stroke_width(self) -> float: - return self._get_attr("strokeWidth", data_type="float", def_value=1.0) - - @stroke_width.setter - def stroke_width(self, value: OptionalNumber): - self._set_attr("strokeWidth", value) - - # curved - @property - def curved(self) -> bool: - return self._get_attr("curved", data_type="bool", def_value=False) - - @curved.setter - def curved(self, value: Optional[bool]): - self._set_attr("curved", value) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # gradient - @property - def gradient(self) -> Optional[Gradient]: - return self.__gradient - - @gradient.setter - def gradient(self, value: Optional[Gradient]): - self.__gradient = value - - # stroke_cap_round - @property - def stroke_cap_round(self) -> bool: - return self._get_attr("strokeCapRound", data_type="bool", def_value=False) - - @stroke_cap_round.setter - def stroke_cap_round(self, value: Optional[bool]): - self._set_attr("strokeCapRound", value) - - # prevent_curve_over_shooting - @property - def prevent_curve_over_shooting(self) -> bool: - return self._get_attr( - "preventCurveOverShooting", data_type="bool", def_value=False - ) - - @prevent_curve_over_shooting.setter - def prevent_curve_over_shooting(self, value: Optional[bool]): - self._set_attr("preventCurveOverShooting", value) - - # prevent_curve_over_shooting_threshold - @property - def prevent_curve_over_shooting_threshold(self) -> OptionalNumber: - return self._get_attr("preventCurveOverShootingThreshold", data_type="float") - - @prevent_curve_over_shooting_threshold.setter - def prevent_curve_over_shooting_threshold(self, value: OptionalNumber): - self._set_attr("preventCurveOverShootingThreshold", value) - - # dash_pattern - @property - def dash_pattern(self): - return self.__dash_pattern - - @dash_pattern.setter - def dash_pattern(self, value: Optional[List[int]]): - self.__dash_pattern = value - - # shadow - @property - def shadow(self): - return self.__shadow - - @shadow.setter - def shadow(self, value: Optional[BoxShadow]): - self.__shadow = value - - # point - @property - def point(self): - return self.__point - - @point.setter - def point(self, value: Union[None, bool, ChartPointShape]): - self.__point = value - - # selected_point - @property - def selected_point(self): - return self.__selected_point - - @selected_point.setter - def selected_point(self, value: Union[None, bool, ChartPointShape]): - self.__selected_point = value - - # above_line_bgcolor - @property - def above_line_bgcolor(self) -> Optional[str]: - return self.__above_line_bgcolor - - @above_line_bgcolor.setter - def above_line_bgcolor(self, value: Optional[str]): - self.__above_line_bgcolor = value - self._set_enum_attr("aboveLineBgcolor", value, ColorEnums) - - # above_line_gradient - @property - def above_line_gradient(self) -> Optional[Gradient]: - return self.__above_line_gradient - - @above_line_gradient.setter - def above_line_gradient(self, value: Optional[Gradient]): - self.__above_line_gradient = value - - # above_line_cutoff_y - @property - def above_line_cutoff_y(self) -> OptionalNumber: - return self._get_attr("aboveLineCutoffY", data_type="float") - - @above_line_cutoff_y.setter - def above_line_cutoff_y(self, value: OptionalNumber): - self._set_attr("aboveLineCutoffY", value) - - # above_line - @property - def above_line(self) -> Optional[ChartPointLine]: - return self.__above_line - - @above_line.setter - def above_line(self, value: Optional[ChartPointLine]): - self.__above_line = value - - # below_line_bgcolor - @property - def below_line_bgcolor(self) -> Optional[str]: - return self.__below_line_bgcolor - - @below_line_bgcolor.setter - def below_line_bgcolor(self, value: Optional[str]): - self.__below_line_bgcolor = value - self._set_enum_attr("belowLineBgcolor", value, ColorEnums) - - # below_line_gradient - @property - def below_line_gradient(self) -> Optional[Gradient]: - return self.__below_line_gradient - - @below_line_gradient.setter - def below_line_gradient(self, value: Optional[Gradient]): - self.__below_line_gradient = value - - # below_line_cutoff_y - @property - def below_line_cutoff_y(self) -> OptionalNumber: - return self._get_attr("belowLineCutoffY", data_type="float") - - @below_line_cutoff_y.setter - def below_line_cutoff_y(self, value: OptionalNumber): - self._set_attr("belowLineCutoffY", value) - - # below_line - @property - def below_line(self) -> Optional[ChartPointLine]: - return self.__below_line - - @below_line.setter - def below_line(self, value: Optional[ChartPointLine]): - self.__below_line = value - - # selected_below_line - @property - def selected_below_line(self) -> Union[None, bool, ChartPointLine]: - return self.__selected_below_line - - @selected_below_line.setter - def selected_below_line(self, value: Union[None, bool, ChartPointLine]): - self.__selected_below_line = value diff --git a/sdk/python/packages/flet/src/flet/core/charts/line_chart_data_point.py b/sdk/python/packages/flet/src/flet/core/charts/line_chart_data_point.py deleted file mode 100644 index fe3f36f17..000000000 --- a/sdk/python/packages/flet/src/flet/core/charts/line_chart_data_point.py +++ /dev/null @@ -1,182 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.badge import BadgeValue -from flet.core.charts.chart_point_line import ChartPointLine -from flet.core.charts.chart_point_shape import ChartPointShape -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.types import TextAlign - - -class LineChartDataPoint(Control): - def __init__( - self, - x: OptionalNumber = None, - y: OptionalNumber = None, - selected: Optional[bool] = None, - show_tooltip: Optional[bool] = None, - tooltip: Optional[str] = None, - tooltip_style: Optional[TextStyle] = None, - tooltip_align: Optional[TextAlign] = None, - point: Union[None, bool, ChartPointShape] = None, - selected_point: Union[None, bool, ChartPointShape] = None, - show_above_line: Optional[bool] = None, - show_below_line: Optional[bool] = None, - selected_below_line: Union[None, bool, ChartPointLine] = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - badge: Optional[BadgeValue] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - badge=badge, - ) - - self.x = x - self.y = y - self.selected = selected - self.show_tooltip = show_tooltip - self.tooltip = tooltip - self.tooltip_align = tooltip_align - self.tooltip_style = tooltip_style - self.point = point - self.selected_point = selected_point - self.show_above_line = show_above_line - self.show_below_line = show_below_line - self.selected_below_line = selected_below_line - - def _get_control_name(self): - return "p" - - def before_update(self): - super().before_update() - self._set_attr_json("tooltipStyle", self.__tooltip_style) - self._set_attr_json("point", self.__point) - self._set_attr_json("selectedPoint", self.__selected_point) - self._set_attr_json("selectedBelowLine", self.__selected_below_line) - - def _get_children(self): - children = [] - return children - - # x - @property - def x(self) -> float: - return self._get_attr("x", data_type="float", def_value=0.0) - - @x.setter - def x(self, value: OptionalNumber): - self._set_attr("x", value) - - # y - @property - def y(self) -> float: - return self._get_attr("y", data_type="float", def_value=0.0) - - @y.setter - def y(self, value: OptionalNumber): - self._set_attr("y", value) - - # selected - @property - def selected(self) -> bool: - return self._get_attr("selected", data_type="bool", def_value=False) - - @selected.setter - def selected(self, value: Optional[bool]): - self._set_attr("selected", value) - - # show_tooltip - @property - def show_tooltip(self) -> bool: - return self._get_attr("showTooltip", data_type="bool", def_value=True) - - @show_tooltip.setter - def show_tooltip(self, value: Optional[bool]): - self._set_attr("showTooltip", value) - - # tooltip - @property - def tooltip(self) -> Optional[str]: - return self._get_attr("tooltip") - - @tooltip.setter - def tooltip(self, value: Optional[str]): - self._set_attr("tooltip", value) - - # tooltip_align - @property - def tooltip_align(self) -> Optional[TextAlign]: - return self.__tooltip_align - - @tooltip_align.setter - def tooltip_align(self, value: Optional[TextAlign]): - self.__tooltip_align = value - self._set_attr( - "tooltipAlign", value.value if isinstance(value, TextAlign) else value - ) - - # tooltip_style - @property - def tooltip_style(self): - return self.__tooltip_style - - @tooltip_style.setter - def tooltip_style(self, value: Optional[TextStyle]): - self.__tooltip_style = value - - # point - @property - def point(self): - return self.__point - - @point.setter - def point(self, value: Union[None, bool, ChartPointShape]): - self.__point = value - - # selected_point - @property - def selected_point(self): - return self.__selected_point - - @selected_point.setter - def selected_point(self, value: Union[None, bool, ChartPointShape]): - self.__selected_point = value - - # show_above_line - @property - def show_above_line(self) -> bool: - return self._get_attr("showAboveLine", data_type="bool", def_value=True) - - @show_above_line.setter - def show_above_line(self, value: Optional[bool]): - self._set_attr("showAboveLine", value) - - # show_below_line - @property - def show_below_line(self) -> bool: - return self._get_attr("showBelowLine", data_type="bool", def_value=True) - - @show_below_line.setter - def show_below_line(self, value: Optional[bool]): - self._set_attr("showBelowLine", value) - - # selected_below_line - @property - def selected_below_line(self) -> Union[None, bool, ChartPointLine]: - return self.__selected_below_line - - @selected_below_line.setter - def selected_below_line(self, value: Union[None, bool, ChartPointLine]): - self.__selected_below_line = value diff --git a/sdk/python/packages/flet/src/flet/core/charts/pie_chart.py b/sdk/python/packages/flet/src/flet/core/charts/pie_chart.py deleted file mode 100644 index 70370a4bd..000000000 --- a/sdk/python/packages/flet/src/flet/core/charts/pie_chart.py +++ /dev/null @@ -1,215 +0,0 @@ -import json -from enum import Enum -from typing import Any, List, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.charts.pie_chart_section import PieChartSection -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class PieChart(ConstrainedControl): - def __init__( - self, - sections: Optional[List[PieChartSection]] = None, - center_space_color: Optional[ColorValue] = None, - center_space_radius: OptionalNumber = None, - sections_space: OptionalNumber = None, - start_degree_offset: OptionalNumber = None, - animate: Optional[AnimationValue] = None, - on_chart_event: OptionalEventCallable["PieChartEvent"] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.__on_chart_event = EventHandler(lambda e: PieChartEvent(e)) - self._add_event_handler("chart_event", self.__on_chart_event.get_handler()) - - self.sections = sections - self.center_space_color = center_space_color - self.center_space_radius = center_space_radius - self.sections_space = sections_space - self.start_degree_offset = start_degree_offset - self.animate = animate - self.on_chart_event = on_chart_event - - def _get_control_name(self): - return "piechart" - - def before_update(self): - super().before_update() - self._set_attr_json("animate", self.__animate) - - def _get_children(self): - children = [] - for ds in self.__sections: - children.append(ds) - return children - - # sections - @property - def sections(self): - return self.__sections - - @sections.setter - def sections(self, value): - self.__sections = value if value is not None else [] - - # center_space_color - @property - def center_space_color(self) -> Optional[ColorValue]: - return self.__center_space_color - - @center_space_color.setter - def center_space_color(self, value: Optional[ColorValue]): - self.__center_space_color = value - self._set_enum_attr("centerSpaceColor", value, ColorEnums) - - # center_space_radius - @property - def center_space_radius(self) -> OptionalNumber: - return self._get_attr("centerSpaceRadius", data_type="float") - - @center_space_radius.setter - def center_space_radius(self, value: OptionalNumber): - self._set_attr("centerSpaceRadius", value) - - # sections_space - @property - def sections_space(self) -> OptionalNumber: - return self._get_attr("sectionsSpace", data_type="float") - - @sections_space.setter - def sections_space(self, value: OptionalNumber): - self._set_attr("sectionsSpace", value) - - # start_degree_offset - @property - def start_degree_offset(self) -> OptionalNumber: - return self._get_attr("startDegreeOffset", data_type="float") - - @start_degree_offset.setter - def start_degree_offset(self, value: OptionalNumber): - self._set_attr("startDegreeOffset", value) - - # animate - @property - def animate(self) -> AnimationValue: - return self.__animate - - @animate.setter - def animate(self, value: AnimationValue): - self.__animate = value - - # on_chart_event - @property - def on_chart_event(self) -> OptionalEventCallable["PieChartEvent"]: - return self.__on_chart_event.handler - - @on_chart_event.setter - def on_chart_event(self, handler: OptionalEventCallable["PieChartEvent"]): - self.__on_chart_event.handler = handler - self._set_attr("onChartEvent", True if handler is not None else None) - - -class PieChartEventType(Enum): - POINTER_ENTER = "pointerEnter" - POINTER_EXIT = "pointerExit" - POINTER_HOVER = "pointerHover" - PAN_CANCEL = "panCancel" - PAN_DOWN = "panDown" - PAN_END = "panEnd" - PAN_START = "panStart" - PAN_UPDATE = "panUpdate" - LONG_PRESS_END = "longPressEnd" - LONG_PRESS_MOVE_UPDATE = "longPressMoveUpdate" - LONG_PRESS_START = "longPressStart" - TAP_CANCEL = "tapCancel" - TAP_DOWN = "tapDown" - TAP_UP = "tapUp" - UNDEFINED = "undefined" - - -class PieChartEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.type: PieChartEventType = PieChartEventType(d.get("type")) - self.section_index: int = d.get("section_index") - self.local_x: Optional[float] = d.get("lx") - self.local_y: Optional[float] = d.get("ly") - # self.radius: float = d["radius"] - # self.angle: float = d["angle"] diff --git a/sdk/python/packages/flet/src/flet/core/charts/pie_chart_section.py b/sdk/python/packages/flet/src/flet/core/charts/pie_chart_section.py deleted file mode 100644 index 8168011f6..000000000 --- a/sdk/python/packages/flet/src/flet/core/charts/pie_chart_section.py +++ /dev/null @@ -1,144 +0,0 @@ -from typing import Any, Optional - -from flet.core.border import BorderSide -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.types import ColorEnums, ColorValue - - -class PieChartSection(Control): - def __init__( - self, - value: OptionalNumber = None, - radius: OptionalNumber = None, - color: Optional[ColorValue] = None, - border_side: Optional[BorderSide] = None, - title: Optional[str] = None, - title_style: Optional[TextStyle] = None, - title_position: OptionalNumber = None, - badge: Optional[Control] = None, - badge_position: OptionalNumber = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - self.value = value - self.radius = radius - self.color = color - self.border_side = border_side - self.title = title - self.title_style = title_style - self.title_position = title_position - self.badge = badge - self.badge_position = badge_position - - def _get_control_name(self): - return "section" - - def before_update(self): - super().before_update() - self._set_attr_json("borderSide", self.__border_side) - self._set_attr_json("titleStyle", self.__title_style) - - def _get_children(self): - children = [] - if self.__badge: - self.__badge._set_attr_internal("n", "badge") - children.append(self.__badge) - return children - - # value - @property - def value(self) -> OptionalNumber: - return self._get_attr("value", data_type="float") - - @value.setter - def value(self, value: OptionalNumber): - self._set_attr("value", value) - - # radius - @property - def radius(self) -> OptionalNumber: - return self._get_attr("radius", data_type="float") - - @radius.setter - def radius(self, value: OptionalNumber): - self._set_attr("radius", value) - - # border_side - @property - def border_side(self) -> Optional[BorderSide]: - return self.__border_side - - @border_side.setter - def border_side(self, value: Optional[BorderSide]): - self.__border_side = value - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # badge - @property - def badge(self) -> Optional[Control]: - return self.__badge - - @badge.setter - def badge(self, value: Optional[Control]): - self.__badge = value - - # badge_position - @property - def badge_position(self) -> OptionalNumber: - return self._get_attr("badgePosition", data_type="float") - - @badge_position.setter - def badge_position(self, value: OptionalNumber): - self._set_attr("badgePosition", value) - - # title - @property - def title(self): - return self._get_attr("title") - - @title.setter - def title(self, value: Optional[str]): - self._set_attr("title", value) - - # title_style - @property - def title_style(self): - return self.__title_style - - @title_style.setter - def title_style(self, value: Optional[TextStyle]): - self.__title_style = value - - # title_position - @property - def title_position(self) -> float: - return self._get_attr("titlePosition", data_type="float", def_value=1.0) - - @title_position.setter - def title_position(self, value: OptionalNumber): - self._set_attr("titlePosition", value) diff --git a/sdk/python/packages/flet/src/flet/core/checkbox.py b/sdk/python/packages/flet/src/flet/core/checkbox.py deleted file mode 100644 index cbf61cd38..000000000 --- a/sdk/python/packages/flet/src/flet/core/checkbox.py +++ /dev/null @@ -1,397 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.border import BorderSide -from flet.core.buttons import OutlinedBorder -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - ControlStateValue, - LabelPosition, - MouseCursor, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, - VisualDensity, -) - - -class Checkbox(ConstrainedControl, AdaptiveControl): - """ - Checkbox allows to select one or more items from a group, or switch between two mutually exclusive options (checked or unchecked, on or off). - - Example: - ``` - import flet as ft - - def main(page): - def button_clicked(e): - t.value = ( - f"Checkboxes values are: {c1.value}, {c2.value}, {c3.value}, {c4.value}, {c5.value}." - ) - page.update() - - t = ft.Text() - c1 = ft.Checkbox(label="Unchecked by default checkbox", value=False) - c2 = ft.Checkbox(label="Undefined by default tristate checkbox", tristate=True) - c3 = ft.Checkbox(label="Checked by default checkbox", value=True) - c4 = ft.Checkbox(label="Disabled checkbox", disabled=True) - c5 = ft.Checkbox( - label="Checkbox with rendered label_position='left'", label_position=ft.LabelPosition.LEFT - ) - b = ft.ElevatedButton(text="Submit", on_click=button_clicked) - page.add(c1, c2, c3, c4, c5, b, t) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/checkbox - """ - - def __init__( - self, - label: Optional[Union[str, Control]] = None, - value: Optional[bool] = None, - label_position: Optional[LabelPosition] = None, - label_style: Optional[TextStyle] = None, - tristate: Optional[bool] = None, - autofocus: Optional[bool] = None, - fill_color: ControlStateValue[ColorValue] = None, - overlay_color: ControlStateValue[ColorValue] = None, - check_color: Optional[ColorValue] = None, - active_color: Optional[ColorValue] = None, - hover_color: Optional[ColorValue] = None, - focus_color: Optional[ColorValue] = None, - semantics_label: Optional[str] = None, - shape: Optional[OutlinedBorder] = None, - splash_radius: OptionalNumber = None, - border_side: ControlStateValue[BorderSide] = None, - is_error: Optional[bool] = None, - visual_density: Optional[VisualDensity] = None, - mouse_cursor: Optional[MouseCursor] = None, - on_change: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl and AdaptiveControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.value = value - self.tristate = tristate - self.label = label - self.label_style = label_style - self.label_position = label_position - self.autofocus = autofocus - self.check_color = check_color - self.fill_color = fill_color - self.focus_color = focus_color - self.hover_color = hover_color - self.overlay_color = overlay_color - self.active_color = active_color - self.semantics_label = semantics_label - self.shape = shape - self.splash_radius = splash_radius - self.border_side = border_side - self.is_error = is_error - self.on_change = on_change - self.on_focus = on_focus - self.on_blur = on_blur - self.visual_density = visual_density - self.mouse_cursor = mouse_cursor - - def _get_control_name(self): - return "checkbox" - - def before_update(self): - super().before_update() - self._set_attr_json("fillColor", self.__fill_color, wrap_attr_dict=True) - self._set_attr_json("overlayColor", self.__overlay_color, wrap_attr_dict=True) - self._set_attr_json("borderSide", self.__border_side, wrap_attr_dict=True) - self._set_attr_json("shape", self.__shape) - self._set_attr_json("labelStyle", self.__label_style) - - def _get_children(self): - return [self.__label] if isinstance(self.__label, Control) else [] - - # value - @property - def value(self) -> Optional[bool]: - return self._get_attr( - "value", data_type="bool?", def_value=False if not self.tristate else None - ) - - @value.setter - def value(self, value: Optional[bool]): - self._set_attr("value", value) - - # tristate - @property - def tristate(self) -> bool: - return self._get_attr("tristate", data_type="bool", def_value=False) - - @tristate.setter - def tristate(self, value: Optional[bool]): - self._set_attr("tristate", value) - - # label - @property - def label(self) -> Optional[Union[str, Control]]: - return self.__label - - @label.setter - def label(self, value: Optional[Union[str, Control]]): - self.__label = value - if not isinstance(value, Control): - self._set_attr("label", value) - - # label_position - @property - def label_position(self) -> Optional[LabelPosition]: - return self.__label_position - - @label_position.setter - def label_position(self, value: Optional[LabelPosition]): - self.__label_position = value - self._set_enum_attr("labelPosition", value, LabelPosition) - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self.__mouse_cursor - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self.__mouse_cursor = value - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # visual_density - @property - def visual_density(self) -> Optional[VisualDensity]: - return self.__visual_density - - @visual_density.setter - def visual_density(self, value: Optional[VisualDensity]): - self.__visual_density = value - self._set_enum_attr("visualDensity", value, VisualDensity) - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # check_color - @property - def check_color(self) -> Optional[ColorValue]: - return self.__check_color - - @check_color.setter - def check_color(self, value: Optional[ColorValue]): - self.__check_color = value - self._set_enum_attr("checkColor", value, ColorEnums) - - # active_color - @property - def active_color(self) -> Optional[ColorValue]: - return self.__active_color - - @active_color.setter - def active_color(self, value: Optional[ColorValue]): - self.__active_color = value - self._set_enum_attr("activeColor", value, ColorEnums) - - # focus_color - @property - def focus_color(self) -> Optional[ColorValue]: - return self.__focus_color - - @focus_color.setter - def focus_color(self, value: Optional[ColorValue]): - self.__focus_color = value - self._set_enum_attr("focusColor", value, ColorEnums) - - # hover_color - @property - def hover_color(self) -> Optional[ColorValue]: - return self.__hover_color - - @hover_color.setter - def hover_color(self, value: Optional[ColorValue]): - self.__hover_color = value - self._set_enum_attr("hoverColor", value, ColorEnums) - - # fill_color - @property - def fill_color(self) -> ControlStateValue[ColorValue]: - return self.__fill_color - - @fill_color.setter - def fill_color(self, value: ControlStateValue[ColorValue]): - self.__fill_color = value - - # overlay_color - @property - def overlay_color(self) -> ControlStateValue[ColorValue]: - return self.__overlay_color - - @overlay_color.setter - def overlay_color(self, value: ControlStateValue[ColorValue]): - self.__overlay_color = value - - # label_style - @property - def label_style(self) -> Optional[TextStyle]: - return self.__label_style - - @label_style.setter - def label_style(self, value: Optional[TextStyle]): - self.__label_style = value - - # semantics_label - @property - def semantics_label(self) -> Optional[str]: - return self._get_attr("semanticsLabel") - - @semantics_label.setter - def semantics_label(self, value: Optional[str]): - self._set_attr("semanticsLabel", value) - - # shape - @property - def shape(self) -> Optional[OutlinedBorder]: - return self.__shape - - @shape.setter - def shape(self, value: Optional[OutlinedBorder]): - self.__shape = value - - # splash_radius - @property - def splash_radius(self) -> Optional[float]: - return self._get_attr("splashRadius", data_type="float") - - @splash_radius.setter - def splash_radius(self, value: OptionalNumber): - self._set_attr("splashRadius", value) - - # is_error - @property - def is_error(self) -> bool: - return self._get_attr("isError", data_type="bool", def_value=False) - - @is_error.setter - def is_error(self, value: Optional[bool]): - self._set_attr("isError", value) - - # border_side - @property - def border_side(self) -> ControlStateValue[BorderSide]: - return self.__border_side - - @border_side.setter - def border_side(self, value: ControlStateValue[BorderSide]): - self.__border_side = value - - # on_change - @property - def on_change(self): - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - - # on_focus - @property - def on_focus(self): - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self): - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) diff --git a/sdk/python/packages/flet/src/flet/core/chip.py b/sdk/python/packages/flet/src/flet/core/chip.py deleted file mode 100644 index b3e414e34..000000000 --- a/sdk/python/packages/flet/src/flet/core/chip.py +++ /dev/null @@ -1,583 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationStyle, AnimationValue -from flet.core.badge import BadgeValue -from flet.core.border import BorderSide -from flet.core.box import BoxConstraints -from flet.core.buttons import OutlinedBorder -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ClipBehavior, - ColorEnums, - ColorValue, - ControlStateValue, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, - VisualDensity, -) - - -class Chip(ConstrainedControl): - """ - Chips are compact elements that represent an attribute, text, entity, or action. - - Example: - ``` - import flet as ft - - - def main(page: ft.Page): - def save_to_favorites_clicked(e): - e.control.label.value = "Saved to favorites" - e.control.leading = ft.Icon(ft.icons.FAVORITE_OUTLINED) - e.control.disabled = True - page.update() - - def open_google_maps(e): - page.launch_url("https://maps.google.com") - page.update() - - save_to_favourites = ft.Chip( - label=ft.Text("Save to favourites"), - leading=ft.Icon(ft.icons.FAVORITE_BORDER_OUTLINED), - bgcolor=ft.colors.GREEN_200, - disabled_color=ft.colors.GREEN_100, - autofocus=True, - on_click=save_to_favorites_clicked, - ) - - open_in_maps = ft.Chip( - label=ft.Text("9 min walk"), - leading=ft.Icon(ft.icons.MAP_SHARP), - bgcolor=ft.colors.GREEN_200, - on_click=open_google_maps, - ) - - page.add(ft.Row([save_to_favourites, open_in_maps])) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/chip - """ - - def __init__( - self, - label: Control, - leading: Optional[Control] = None, - selected: Optional[bool] = False, - selected_color: Optional[ColorValue] = None, - elevation: OptionalNumber = None, - bgcolor: Optional[ColorValue] = None, - show_checkmark: Optional[bool] = None, - check_color: Optional[ColorValue] = None, - shadow_color: Optional[ColorValue] = None, - shape: Optional[OutlinedBorder] = None, - padding: Optional[PaddingValue] = None, - delete_icon: Optional[Control] = None, - delete_icon_tooltip: Optional[str] = None, - delete_icon_color: Optional[ColorValue] = None, - disabled_color: Optional[ColorValue] = None, - label_padding: Optional[PaddingValue] = None, - label_style: Optional[TextStyle] = None, - selected_shadow_color: Optional[ColorValue] = None, - autofocus: Optional[bool] = None, - surface_tint_color: Optional[ColorValue] = None, - color: ControlStateValue[ColorValue] = None, - click_elevation: OptionalNumber = None, - clip_behavior: Optional[ClipBehavior] = None, - visual_density: Optional[VisualDensity] = None, - border_side: Optional[BorderSide] = None, - leading_size_constraints: Optional[BoxConstraints] = None, - delete_icon_size_constraints: Optional[BoxConstraints] = None, - enable_animation_style: Optional[AnimationStyle] = None, - select_animation_style: Optional[AnimationStyle] = None, - leading_drawer_animation_style: Optional[AnimationStyle] = None, - delete_drawer_animation_style: Optional[AnimationStyle] = None, - on_click: OptionalControlEventCallable = None, - on_delete: OptionalControlEventCallable = None, - on_select: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - key: Optional[str] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.autofocus = autofocus - self.label = label - self.leading = leading - self.bgcolor = bgcolor - self.check_color = check_color - self.selected = selected - self.delete_icon_tooltip = delete_icon_tooltip - self.delete_icon = delete_icon - self.delete_icon_color = delete_icon_color - self.disabled_color = disabled_color - self.elevation = elevation - self.label_padding = label_padding - self.label_style = label_style - self.padding = padding - self.selected_color = selected_color - self.selected_shadow_color = selected_shadow_color - self.shadow_color = shadow_color - self.shape = shape - self.show_checkmark = show_checkmark - self.on_click = on_click - self.on_delete = on_delete - self.on_select = on_select - self.on_focus = on_focus - self.on_blur = on_blur - self.color = color - self.surface_tint_color = surface_tint_color - self.click_elevation = click_elevation - self.clip_behavior = clip_behavior - self.visual_density = visual_density - self.border_side = border_side - self.leading_size_constraints = leading_size_constraints - self.delete_icon_size_constraints = delete_icon_size_constraints - self.enable_animation_style = enable_animation_style - self.select_animation_style = select_animation_style - self.leading_drawer_animation_style = leading_drawer_animation_style - self.delete_drawer_animation_style = delete_drawer_animation_style - - def _get_control_name(self): - return "chip" - - def before_update(self): - super().before_update() - self._set_attr_json("labelPadding", self.__label_padding) - self._set_attr_json("labelStyle", self.__label_style) - self._set_attr_json("padding", self.__padding) - self._set_attr_json("shape", self.__shape) - self._set_attr_json("borderSide", self.__border_side) - self._set_attr_json("color", self.__color, wrap_attr_dict=True) - self._set_attr_json("leadingSizeConstraints", self.__leading_size_constraints) - self._set_attr_json( - "deleteIconSizeConstraints", self.__delete_icon_size_constraints - ) - self._set_attr_json("enableAnimationStyle", self.__enable_animation_style) - self._set_attr_json("selectAnimationStyle", self.__select_animation_style) - self._set_attr_json( - "leadingDrawerAnimationStyle", self.__leading_drawer_animation_style - ) - self._set_attr_json( - "deleteDrawerAnimationStyle", self.__delete_drawer_animation_style - ) - - def _get_children(self): - self.__label._set_attr_internal("n", "label") - children = [self.__label] - if self.__leading: - self.__leading._set_attr_internal("n", "leading") - children.append(self.__leading) - if self.__delete_icon: - self.__delete_icon._set_attr_internal("n", "deleteIcon") - children.append(self.__delete_icon) - return children - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # delete_icon_size_constraints - @property - def delete_icon_size_constraints(self) -> Optional[BoxConstraints]: - return self.__delete_icon_size_constraints - - @delete_icon_size_constraints.setter - def delete_icon_size_constraints(self, value: Optional[BoxConstraints]): - self.__delete_icon_size_constraints = value - - # leading_size_constraints - @property - def leading_size_constraints(self) -> Optional[BoxConstraints]: - return self.__leading_size_constraints - - @leading_size_constraints.setter - def leading_size_constraints(self, value: Optional[BoxConstraints]): - self.__leading_size_constraints = value - - # enable_animation_style - @property - def enable_animation_style(self) -> Optional[AnimationStyle]: - return self.__enable_animation_style - - @enable_animation_style.setter - def enable_animation_style(self, value: Optional[AnimationStyle]): - self.__enable_animation_style = value - - # select_animation_style - @property - def select_animation_style(self) -> Optional[AnimationStyle]: - return self.__select_animation_style - - @select_animation_style.setter - def select_animation_style(self, value: Optional[AnimationStyle]): - self.__select_animation_style = value - - # leading_drawer_animation_style - @property - def leading_drawer_animation_style(self) -> Optional[AnimationStyle]: - return self.__leading_drawer_animation_style - - @leading_drawer_animation_style.setter - def leading_drawer_animation_style(self, value: Optional[AnimationStyle]): - self.__leading_drawer_animation_style = value - - # delete_drawer_animation_style - @property - def delete_drawer_animation_style(self) -> Optional[AnimationStyle]: - return self.__delete_drawer_animation_style - - @delete_drawer_animation_style.setter - def delete_drawer_animation_style(self, value: Optional[AnimationStyle]): - self.__delete_drawer_animation_style = value - - # selected - @property - def selected(self) -> bool: - return self._get_attr("selected", data_type="bool", def_value=False) - - @selected.setter - def selected(self, value: Optional[bool]): - self._set_attr("selected", value) - - # show_checkmark - @property - def show_checkmark(self) -> bool: - return self._get_attr("showCheckmark", data_type="bool", def_value=True) - - @show_checkmark.setter - def show_checkmark(self, value: Optional[bool]): - self._set_attr("showCheckmark", value) - - # delete_icon_tooltip - @property - def delete_icon_tooltip(self) -> Optional[str]: - return self._get_attr("deleteButtonTooltip") - - @delete_icon_tooltip.setter - def delete_icon_tooltip(self, value: Optional[str]): - self._set_attr("deleteButtonTooltip", value) - - # label - @property - def label(self) -> Control: - return self.__label - - @label.setter - def label(self, value: Control): - self.__label = value - - # label_padding - @property - def label_padding(self) -> Optional[PaddingValue]: - return self.__label_padding - - @label_padding.setter - def label_padding(self, value: Optional[PaddingValue]): - self.__label_padding = value - - # label_style - @property - def label_style(self) -> Optional[TextStyle]: - return self.__label_style - - @label_style.setter - def label_style(self, value: Optional[TextStyle]): - self.__label_style = value - - # leading - @property - def leading(self) -> Optional[Control]: - return self.__leading - - @leading.setter - def leading(self, value: Optional[Control]): - self.__leading = value - - # delete_icon - @property - def delete_icon(self) -> Optional[Control]: - return self.__delete_icon - - @delete_icon.setter - def delete_icon(self, value: Optional[Control]): - self.__delete_icon = value - - # delete_icon_color - @property - def delete_icon_color(self) -> Optional[ColorValue]: - return self.__delete_icon_color - - @delete_icon_color.setter - def delete_icon_color(self, value: Optional[ColorValue]): - self.__delete_icon_color = value - self._set_enum_attr("deleteIconColor", value, ColorEnums) - - # disabled_color - @property - def disabled_color(self) -> Optional[ColorValue]: - return self.__disabled_color - - @disabled_color.setter - def disabled_color(self, value: Optional[ColorValue]): - self.__disabled_color = value - self._set_enum_attr("disabledColor", value, ColorEnums) - - # color - @property - def color(self) -> ControlStateValue[str]: - return self.__color - - @color.setter - def color(self, value: ControlStateValue[str]): - self.__color = value - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # check_color - @property - def check_color(self) -> Optional[ColorValue]: - return self.__check_color - - @check_color.setter - def check_color(self, value: Optional[ColorValue]): - self.__check_color = value - self._set_enum_attr("checkColor", value, ColorEnums) - - # selected_color - @property - def selected_color(self) -> Optional[ColorValue]: - return self.__selected_color - - @selected_color.setter - def selected_color(self, value: Optional[ColorValue]): - self.__selected_color = value - self._set_enum_attr("selectedColor", value, ColorEnums) - - # selected_shadow_color - @property - def selected_shadow_color(self) -> Optional[ColorValue]: - return self.__selected_shadow_color - - @selected_shadow_color.setter - def selected_shadow_color(self, value: Optional[ColorValue]): - self.__selected_shadow_color = value - self._set_enum_attr("selectedShadowColor", value, ColorEnums) - - # surface_tint_color - @property - def surface_tint_color(self) -> Optional[ColorValue]: - return self.__surface_tint_color - - @surface_tint_color.setter - def surface_tint_color(self, value: Optional[ColorValue]): - self.__surface_tint_color = value - self._set_enum_attr("surfaceTintColor", value, ColorEnums) - - # shadow_color - @property - def shadow_color(self) -> Optional[ColorValue]: - return self.__shadow_color - - @shadow_color.setter - def shadow_color(self, value: Optional[ColorValue]): - self.__shadow_color = value - self._set_enum_attr("shadowColor", value, ColorEnums) - - # elevation - @property - def elevation(self) -> OptionalNumber: - return self._get_attr("elevation", data_type="float") - - @elevation.setter - def elevation(self, value: OptionalNumber): - self._set_attr("elevation", value) - - # click_elevation - @property - def click_elevation(self) -> OptionalNumber: - return self._get_attr("clickElevation", data_type="float") - - @click_elevation.setter - def click_elevation(self, value: OptionalNumber): - self._set_attr("clickElevation", value) - - # shape - @property - def shape(self) -> Optional[OutlinedBorder]: - return self.__shape - - @shape.setter - def shape(self, value: Optional[OutlinedBorder]): - self.__shape = value - - # visual_density - @property - def visual_density(self) -> Optional[VisualDensity]: - return self.__visual_density - - @visual_density.setter - def visual_density(self, value: Optional[VisualDensity]): - self.__visual_density = value - self._set_enum_attr("visualDensity", value, VisualDensity) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # border_side - @property - def border_side(self) -> Optional[BorderSide]: - return self.__border_side - - @border_side.setter - def border_side(self, value: Optional[BorderSide]): - self.__border_side = value - - # on_click - @property - def on_click(self): - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) - self._set_attr("onclick", True if handler is not None else None) - - # on_delete - @property - def on_delete(self): - return self._get_event_handler("delete") - - @on_delete.setter - def on_delete(self, handler: OptionalControlEventCallable): - self._add_event_handler("delete", handler) - self._set_attr("onDelete", True if handler is not None else None) - - # on_select - @property - def on_select(self): - return self._get_event_handler("select") - - @on_select.setter - def on_select(self, handler: OptionalControlEventCallable): - self._add_event_handler("select", handler) - self._set_attr("onSelect", True if handler is not None else None) - - # on_focus - @property - def on_focus(self): - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self): - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) diff --git a/sdk/python/packages/flet/src/flet/core/circle_avatar.py b/sdk/python/packages/flet/src/flet/core/circle_avatar.py deleted file mode 100644 index 94b50faa5..000000000 --- a/sdk/python/packages/flet/src/flet/core/circle_avatar.py +++ /dev/null @@ -1,255 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class CircleAvatar(ConstrainedControl): - """ - A circle that represents a user. - - If `foreground_image_src` fails then `background_image_src` is used. If `background_image_src` fails too, - then `bgcolor` is used. - - Example: - ``` - import flet as ft - - def main(page): - # a "normal" avatar with background image - a1 = ft.CircleAvatar( - foreground_image_src="https://avatars.githubusercontent.com/u/5041459?s=88&v=4", - content=ft.Text("FF"), - ) - # avatar with failing foreground image and fallback text - a2 = ft.CircleAvatar( - foreground_image_src="https://avatars.githubusercontent.com/u/_5041459?s=88&v=4", - content=ft.Text("FF"), - ) - # avatar with icon, aka icon with inverse background - a3 = ft.CircleAvatar( - content=ft.Icon(ft.icons.ABC), - ) - # avatar with icon and custom colors - a4 = ft.CircleAvatar( - content=ft.Icon(ft.icons.WARNING_ROUNDED), - color=ft.colors.YELLOW_200, - bgcolor=ft.colors.AMBER_700, - ) - # avatar with online status - a5 = ft.Stack( - [ - ft.CircleAvatar( - foreground_image_src="https://avatars.githubusercontent.com/u/5041459?s=88&v=4" - ), - ft.Container( - content=ft.CircleAvatar(bgcolor=ft.colors.GREEN, radius=5), - alignment=ft.alignment.bottom_left, - ), - ], - width=40, - height=40, - ) - page.add(a1, a2, a3, a4, a5) - - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/circleavatar - """ - - def __init__( - self, - content: Optional[Control] = None, - foreground_image_src: Optional[str] = None, - background_image_src: Optional[str] = None, - color: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, - radius: OptionalNumber = None, - min_radius: OptionalNumber = None, - max_radius: OptionalNumber = None, - on_image_error: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - key: Optional[str] = None, - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.foreground_image_src = foreground_image_src - self.background_image_src = background_image_src - self.radius = radius - self.min_radius = min_radius - self.max_radius = max_radius - self.color = color - self.bgcolor = bgcolor - self.content = content - self.on_image_error = on_image_error - - def _get_control_name(self): - return "circleavatar" - - def _get_children(self): - if self.__content is not None: - self.__content._set_attr_internal("n", "content") - return [self.__content] - return [] - - # foreground_image_src - @property - def foreground_image_src(self) -> Optional[str]: - return self._get_attr("foregroundImageSrc") - - @foreground_image_src.setter - def foreground_image_src(self, value: Optional[str]): - self._set_attr("foregroundImageSrc", value) - - # background_image_src - @property - def background_image_src(self) -> Optional[str]: - return self._get_attr("backgroundImageSrc") - - @background_image_src.setter - def background_image_src(self, value: Optional[str]): - self._set_attr("backgroundImageSrc", value) - - # radius - @property - def radius(self) -> OptionalNumber: - return self._get_attr("radius", data_type="float") - - @radius.setter - def radius(self, value: OptionalNumber): - assert value is None or value >= 0, "radius cannot be negative" - self._set_attr("radius", value) - - # min_radius - @property - def min_radius(self) -> OptionalNumber: - return self._get_attr("minRadius", data_type="float") - - @min_radius.setter - def min_radius(self, value: OptionalNumber): - assert value is None or value >= 0, "min_radius cannot be negative" - self._set_attr("minRadius", value) - - # max_radius - @property - def max_radius(self) -> OptionalNumber: - return self._get_attr("maxRadius", data_type="float") - - @max_radius.setter - def max_radius(self, value: OptionalNumber): - assert value is None or value >= 0, "max_radius cannot be negative" - self._set_attr("maxRadius", value) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # on_image_error - @property - def on_image_error(self): - return self._get_event_handler("imageError") - - @on_image_error.setter - def on_image_error(self, handler: OptionalControlEventCallable): - self._add_event_handler("imageError", handler) diff --git a/sdk/python/packages/flet/src/flet/core/client_storage.py b/sdk/python/packages/flet/src/flet/core/client_storage.py deleted file mode 100644 index bd8a0fd6e..000000000 --- a/sdk/python/packages/flet/src/flet/core/client_storage.py +++ /dev/null @@ -1,101 +0,0 @@ -import json -from typing import Any, List - - -class ClientStorage: - def __init__(self, page): - self.__page = page - - def set(self, key: str, value: Any) -> bool: - jv = self.__page._convert_attr_json(value) - assert jv is not None - return ( - self.__page._invoke_method( - "clientStorage:set", {"key": key, "value": jv}, wait_for_result=True - ) - == "true" - ) - - async def set_async(self, key: str, value: Any) -> bool: - jv = self.__page._convert_attr_json(value) - assert jv is not None - return ( - await self.__page._invoke_method_async( - "clientStorage:set", {"key": key, "value": jv}, wait_for_result=True - ) - ) == "true" - - def get(self, key: str): - jv = self.__page._invoke_method( - "clientStorage:get", {"key": key}, wait_for_result=True - ) - if jv: - return json.loads(json.loads(jv)) - return None - - async def get_async(self, key: str): - jv = await self.__page._invoke_method_async( - "clientStorage:get", {"key": key}, wait_for_result=True - ) - if jv: - return json.loads(json.loads(jv)) - return None - - def contains_key(self, key: str) -> bool: - return ( - self.__page._invoke_method( - "clientStorage:containskey", {"key": key}, wait_for_result=True - ) - == "true" - ) - - async def contains_key_async(self, key: str) -> bool: - return ( - await self.__page._invoke_method_async( - "clientStorage:containskey", {"key": key}, wait_for_result=True - ) - == "true" - ) - - def remove(self, key: str) -> bool: - return ( - self.__page._invoke_method( - "clientStorage:remove", {"key": key}, wait_for_result=True - ) - == "true" - ) - - async def remove_async(self, key: str) -> bool: - return ( - await self.__page._invoke_method_async( - "clientStorage:remove", {"key": key}, wait_for_result=True - ) - ) == "true" - - def get_keys(self, key_prefix: str) -> List[str]: - jr = self.__page._invoke_method( - "clientStorage:getkeys", {"key_prefix": key_prefix}, wait_for_result=True - ) - assert jr is not None - return json.loads(jr) - - async def get_keys_async(self, key_prefix: str) -> List[str]: - jr = await self.__page._invoke_method_async( - "clientStorage:getkeys", {"key_prefix": key_prefix}, wait_for_result=True - ) - assert jr is not None - return json.loads(jr) - - def clear(self) -> bool: - return ( - self.__page._invoke_method("clientStorage:clear", wait_for_result=True) - == "true" - ) - - async def clear_async(self) -> bool: - return ( - await self.__page._invoke_method_async( - "clientStorage:clear", wait_for_result=True - ) - == "true" - ) diff --git a/sdk/python/packages/flet/src/flet/core/column.py b/sdk/python/packages/flet/src/flet/core/column.py deleted file mode 100644 index 8f0e58cee..000000000 --- a/sdk/python/packages/flet/src/flet/core/column.py +++ /dev/null @@ -1,244 +0,0 @@ -from typing import Any, Callable, Optional, Sequence, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control -from flet.core.ref import Ref -from flet.core.scrollable_control import OnScrollEvent, ScrollableControl -from flet.core.types import ( - CrossAxisAlignment, - MainAxisAlignment, - OffsetValue, - OptionalControlEventCallable, - OptionalNumber, - ResponsiveNumber, - RotateValue, - ScaleValue, - ScrollMode, -) - - -class Column(ConstrainedControl, ScrollableControl, AdaptiveControl): - """ - Container allows to decorate a control with background color and border and position it with padding, margin and alignment. - - Example: - - ``` - import flet as ft - - def main(page: ft.Page): - page.title = "Column example" - - page.add( - ft.Column( - expand=True, - controls=[ - ft.Container( - expand=1, - content=ft.Text("Container 1"), - bgcolor=ft.colors.GREEN_100, - ), - ft.Container( - expand=2, content=ft.Text("Container 2"), bgcolor=ft.colors.RED_100 - ), - ], - ), - ), - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/column - """ - - def __init__( - self, - controls: Optional[Sequence[Control]] = None, - alignment: Optional[MainAxisAlignment] = None, - horizontal_alignment: Optional[CrossAxisAlignment] = None, - spacing: OptionalNumber = None, - tight: Optional[bool] = None, - wrap: Optional[bool] = None, - run_spacing: OptionalNumber = None, - run_alignment: Optional[MainAxisAlignment] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - # - # ScrollableControl and AdaptiveControl - # - scroll: Optional[ScrollMode] = None, - auto_scroll: Optional[bool] = None, - on_scroll_interval: OptionalNumber = None, - on_scroll: Optional[Callable[[OnScrollEvent], None]] = None, - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - rtl=rtl, - ) - - ScrollableControl.__init__( - self, - scroll=scroll, - auto_scroll=auto_scroll, - on_scroll_interval=on_scroll_interval, - on_scroll=on_scroll, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.controls = controls - self.horizontal_alignment = horizontal_alignment - self.alignment = alignment - self.spacing = spacing - self.tight = tight - self.wrap = wrap - self.run_spacing = run_spacing - self.run_alignment = run_alignment - - def _get_control_name(self): - return "column" - - def _get_children(self): - return self.__controls - - def __contains__(self, item): - return item in self.__controls - - # Public methods - def clean(self): - super().clean() - self.__controls.clear() - - # tight - @property - def tight(self) -> bool: - return self._get_attr("tight", data_type="bool", def_value=False) - - @tight.setter - def tight(self, value: Optional[bool]): - self._set_attr("tight", value) - - # alignment - @property - def alignment(self) -> Optional[MainAxisAlignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[MainAxisAlignment]): - self.__alignment = value - self._set_enum_attr("alignment", value, MainAxisAlignment) - - # run_alignment - @property - def run_alignment(self) -> Optional[MainAxisAlignment]: - return self.__run_alignment - - @run_alignment.setter - def run_alignment(self, value: Optional[MainAxisAlignment]): - self.__run_alignment = value - self._set_enum_attr("runAlignment", value, MainAxisAlignment) - - # horizontal_alignment - @property - def horizontal_alignment(self) -> Optional[CrossAxisAlignment]: - return self.__horizontal_alignment - - @horizontal_alignment.setter - def horizontal_alignment(self, value: Optional[CrossAxisAlignment]): - self.__horizontal_alignment = value - self._set_enum_attr("horizontalAlignment", value, CrossAxisAlignment) - - # spacing - @property - def spacing(self) -> OptionalNumber: - return self._get_attr("spacing", data_type="float", def_value=10) - - @spacing.setter - def spacing(self, value: OptionalNumber): - self._set_attr("spacing", value) - - # wrap - @property - def wrap(self) -> bool: - return self._get_attr("wrap", data_type="bool", def_value=False) - - @wrap.setter - def wrap(self, value: Optional[bool]): - self._set_attr("wrap", value) - - # run_spacing - @property - def run_spacing(self) -> OptionalNumber: - return self._get_attr("runSpacing", data_type="float") - - @run_spacing.setter - def run_spacing(self, value: OptionalNumber): - self._set_attr("runSpacing", value) - - # controls - @property - def controls(self): - return self.__controls - - @controls.setter - def controls(self, value: Optional[Sequence[Control]]): - self.__controls = list(value) if value is not None else [] diff --git a/sdk/python/packages/flet/src/flet/core/connection.py b/sdk/python/packages/flet/src/flet/core/connection.py deleted file mode 100644 index 31cf512ad..000000000 --- a/sdk/python/packages/flet/src/flet/core/connection.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import List, Optional - -from flet.core.protocol import Command -from flet.core.pubsub.pubsub_hub import PubSubHub - - -class Connection: - def __init__(self): - self.page_name: str = "" - self.page_url: Optional[str] = None - self.sessions = {} - self.pubsubhub = PubSubHub() - - def send_command(self, session_id: str, command: Command): - raise NotImplementedError() - - def send_commands(self, session_id: str, commands: List[Command]): - raise NotImplementedError() - - def _get_ws_url(self, server: str): - url = server.rstrip("/") - if server.startswith("https://"): - url = url.replace("https://", "wss://") - elif server.startswith("http://"): - url = url.replace("http://", "ws://") - else: - url = "ws://" + url - return url + "/ws" - - def dispose(self): - pass diff --git a/sdk/python/packages/flet/src/flet/core/constrained_control.py b/sdk/python/packages/flet/src/flet/core/constrained_control.py deleted file mode 100644 index 798164157..000000000 --- a/sdk/python/packages/flet/src/flet/core/constrained_control.py +++ /dev/null @@ -1,263 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class ConstrainedControl(Control): - def __init__( - self, - ref: Optional[Ref] = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - # - # ConstrainedControl specific - # - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - ): - Control.__init__( - self, - ref=ref, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - rtl=rtl, - ) - - self.key = key - self.width = width - self.height = height - self.left = left - self.top = top - self.right = right - self.bottom = bottom - self.scale = scale - self.rotate = rotate - self.offset = offset - self.aspect_ratio = aspect_ratio - self.animate_opacity = animate_opacity - self.animate_size = animate_size - self.animate_position = animate_position - self.animate_rotation = animate_rotation - self.animate_scale = animate_scale - self.animate_offset = animate_offset - self.on_animation_end = on_animation_end - - def before_update(self): - super().before_update() - self._set_attr_json("rotate", self.__rotate) - self._set_attr_json("scale", self.__scale) - self._set_attr_json("offset", self.__offset) - self._set_attr_json("animateOpacity", self.__animate_opacity) - self._set_attr_json("animateSize", self.__animate_size) - self._set_attr_json("animatePosition", self.__animate_position) - self._set_attr_json("animateRotation", self.__animate_rotation) - self._set_attr_json("animateScale", self.__animate_scale) - self._set_attr_json("animateOffset", self.__animate_offset) - - # key - @property - def key(self) -> Optional[str]: - return self._get_attr("key") - - @key.setter - def key(self, value: Optional[str]): - self._set_attr("key", value) - - # width - @property - def width(self) -> OptionalNumber: - """ - Control width. - """ - return self._get_attr("width") - - @width.setter - def width(self, value: OptionalNumber): - self._set_attr("width", value) - - # height - @property - def height(self) -> OptionalNumber: - return self._get_attr("height") - - @height.setter - def height(self, value: OptionalNumber): - self._set_attr("height", value) - - # left - @property - def left(self) -> OptionalNumber: - return self._get_attr("left") - - @left.setter - def left(self, value: OptionalNumber): - self._set_attr("left", value) - - # top - @property - def top(self) -> OptionalNumber: - return self._get_attr("top") - - @top.setter - def top(self, value: OptionalNumber): - self._set_attr("top", value) - - # right - @property - def right(self) -> OptionalNumber: - return self._get_attr("right") - - @right.setter - def right(self, value: OptionalNumber): - self._set_attr("right", value) - - # bottom - @property - def bottom(self) -> OptionalNumber: - return self._get_attr("bottom") - - @bottom.setter - def bottom(self, value: OptionalNumber): - self._set_attr("bottom", value) - - # rotate - @property - def rotate(self) -> Optional[RotateValue]: - return self.__rotate - - @rotate.setter - def rotate(self, value: Optional[RotateValue]): - self.__rotate = value - - # scale - @property - def scale(self) -> Optional[ScaleValue]: - return self.__scale - - @scale.setter - def scale(self, value: Optional[ScaleValue]): - self.__scale = value - - # offset - @property - def offset(self) -> Optional[OffsetValue]: - return self.__offset - - @offset.setter - def offset(self, value: Optional[OffsetValue]): - self.__offset = value - - # aspect_ratio - @property - def aspect_ratio(self) -> OptionalNumber: - return self._get_attr("aspectRatio") - - @aspect_ratio.setter - def aspect_ratio(self, value: OptionalNumber): - self._set_attr("aspectRatio", value) - - # animate_opacity - @property - def animate_opacity(self) -> Optional[AnimationValue]: - return self.__animate_opacity - - @animate_opacity.setter - def animate_opacity(self, value: Optional[AnimationValue]): - self.__animate_opacity = value - - # animate_size - @property - def animate_size(self) -> Optional[AnimationValue]: - return self.__animate_size - - @animate_size.setter - def animate_size(self, value: Optional[AnimationValue]): - self.__animate_size = value - - # animate_position - @property - def animate_position(self) -> Optional[AnimationValue]: - return self.__animate_position - - @animate_position.setter - def animate_position(self, value: Optional[AnimationValue]): - self.__animate_position = value - - # animate_rotation - @property - def animate_rotation(self) -> Optional[AnimationValue]: - return self.__animate_rotation - - @animate_rotation.setter - def animate_rotation(self, value: Optional[AnimationValue]): - self.__animate_rotation = value - - # animate_scale - @property - def animate_scale(self) -> Optional[AnimationValue]: - return self.__animate_scale - - @animate_scale.setter - def animate_scale(self, value: Optional[AnimationValue]): - self.__animate_scale = value - - # animate_offset - @property - def animate_offset(self) -> Optional[AnimationValue]: - return self.__animate_offset - - @animate_offset.setter - def animate_offset(self, value: Optional[AnimationValue]): - self.__animate_offset = value - - # on_animation_end - @property - def on_animation_end(self) -> OptionalControlEventCallable: - return self._get_event_handler("animation_end") - - @on_animation_end.setter - def on_animation_end(self, handler: OptionalControlEventCallable): - self._add_event_handler("animation_end", handler) - self._set_attr("onAnimationEnd", True if handler is not None else None) diff --git a/sdk/python/packages/flet/src/flet/core/container.py b/sdk/python/packages/flet/src/flet/core/container.py deleted file mode 100644 index 2f404cf45..000000000 --- a/sdk/python/packages/flet/src/flet/core/container.py +++ /dev/null @@ -1,536 +0,0 @@ -import json -from typing import Any, List, Optional, Tuple, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.alignment import Alignment -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.blur import Blur -from flet.core.border import Border -from flet.core.box import ( - BoxDecoration, - BoxShadow, - BoxShape, - ColorFilter, - DecorationImage, -) -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.gradients import Gradient -from flet.core.ref import Ref -from flet.core.theme import Theme -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - BlendMode, - BorderRadiusValue, - ClipBehavior, - ColorEnums, - ColorValue, - ImageFit, - ImageRepeat, - MarginValue, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, - ThemeMode, - UrlTarget, -) -from flet.utils.deprecated import deprecated_property - - -class Container(ConstrainedControl, AdaptiveControl): - """ - Container allows to decorate a control with background color and border and position it with padding, margin and alignment. - - Example: - - ``` - import flet as ft - - def main(page: ft.Page): - page.title = "Container" - - c1 = ft.Container( - content=ft.Text("Container with background"), - bgcolor=ft.colors.AMBER_100, - padding=5, - ) - page.add(c1) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/container - """ - - def __init__( - self, - content: Optional[Control] = None, - padding: Optional[PaddingValue] = None, - margin: Optional[MarginValue] = None, - alignment: Optional[Alignment] = None, - bgcolor: Optional[ColorValue] = None, - gradient: Optional[Gradient] = None, - blend_mode: Optional[BlendMode] = None, - border: Optional[Border] = None, - border_radius: Optional[BorderRadiusValue] = None, - shape: Optional[BoxShape] = None, - clip_behavior: Optional[ClipBehavior] = None, - ink: Optional[bool] = None, - image: Optional[DecorationImage] = None, - ink_color: Optional[ColorValue] = None, - animate: Optional[AnimationValue] = None, - blur: Union[ - None, float, int, Tuple[Union[float, int], Union[float, int]], Blur - ] = None, - shadow: Union[None, BoxShadow, List[BoxShadow]] = None, - url: Optional[str] = None, - url_target: Optional[UrlTarget] = None, - theme: Optional[Theme] = None, - dark_theme: Optional[Theme] = None, - theme_mode: Optional[ThemeMode] = None, - color_filter: Optional[ColorFilter] = None, - ignore_interactions: Optional[bool] = None, - foreground_decoration: Optional[BoxDecoration] = None, - on_click: OptionalControlEventCallable = None, - on_tap_down: OptionalEventCallable["ContainerTapEvent"] = None, - on_long_press: OptionalControlEventCallable = None, - on_hover: OptionalControlEventCallable = None, - # - # ConstrainedControl and AdaptiveControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - rtl=rtl, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.__on_tap_down = EventHandler(lambda e: ContainerTapEvent(e)) - self._add_event_handler("tap_down", self.__on_tap_down.get_handler()) - - self.content = content - self.padding = padding - self.margin = margin - self.alignment = alignment - self.bgcolor = bgcolor - self.gradient = gradient - self.blend_mode = blend_mode - self.border = border - self.border_radius = border_radius - self.shape = shape - self.clip_behavior = clip_behavior - self.ink = ink - self.ink_color = ink_color - self.animate = animate - self.blur = blur - self.shadow = shadow - self.url = url - self.url_target = url_target - self.theme = theme - self.dark_theme = dark_theme - self.theme_mode = theme_mode - self.color_filter = color_filter - self.ignore_interactions = ignore_interactions - self.on_click = on_click - self.on_tap_down = on_tap_down - self.on_long_press = on_long_press - self.on_hover = on_hover - self.image = image - self.foreground_decoration = foreground_decoration - - def _get_control_name(self): - return "container" - - def before_update(self): - super().before_update() - assert ( - self.__blend_mode is None - or self.__gradient is not None - or self.bgcolor is not None - ), "blend_mode applies to bgcolor or gradient, but no bgcolor or gradient was provided" - assert ( - self.__shape != BoxShape.CIRCLE or self.__border_radius is None - ), "border_radius is not supported with shape=BoxShape.CIRCLE" - self._set_attr_json("borderRadius", self.__border_radius) - self._set_attr_json("border", self.__border) - self._set_attr_json("margin", self.__margin) - self._set_attr_json("padding", self.__padding) - self._set_attr_json("alignment", self.__alignment) - self._set_attr_json("gradient", self.__gradient) - self._set_attr_json("animate", self.__animate) - self._set_attr_json("blur", self.__blur) - self._set_attr_json("shadow", self.__shadow if self.__shadow else None) - self._set_attr_json("theme", self.__theme) - self._set_attr_json("darkTheme", self.__dark_theme) - self._set_attr_json("colorFilter", self.__color_filter) - self._set_attr_json("image", self.__image) - self._set_attr_json("foregroundDecoration", self.__foreground_decoration) - - def _get_children(self): - children = [] - if self.__content is not None: - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - return children - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - """:obj:`Alignment`, optional: Align the child control within the container. - - Alignment is an instance of `alignment.Alignment` class object with `x` and `y` properties - representing the distance from the center of a rectangle. - """ - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # image - @property - def image(self) -> Optional[DecorationImage]: - return self.__image - - @image.setter - def image(self, value: Optional[DecorationImage]): - self.__image = value - - # foreground_decoration - @property - def foreground_decoration(self) -> Optional[BoxDecoration]: - return self.__foreground_decoration - - @foreground_decoration.setter - def foreground_decoration(self, value: Optional[BoxDecoration]): - self.__foreground_decoration = value - - # margin - @property - def margin(self) -> Optional[MarginValue]: - return self.__margin - - @margin.setter - def margin(self, value: Optional[MarginValue]): - self.__margin = value - - # bgcolor - @property - def bgcolor(self): - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgColor", value, ColorEnums) - - # gradient - @property - def gradient(self) -> Optional[Gradient]: - return self.__gradient - - @gradient.setter - def gradient(self, value: Optional[Gradient]): - self.__gradient = value - - # blend_mode - @property - def blend_mode(self) -> Optional[BlendMode]: - return self.__blend_mode - - @blend_mode.setter - def blend_mode(self, value: Optional[BlendMode]): - self.__blend_mode = value - self._set_enum_attr("blendMode", value, BlendMode) - - # blur - @property - def blur( - self, - ) -> Union[None, float, int, Tuple[Union[float, int], Union[float, int]], Blur]: - return self.__blur - - @blur.setter - def blur( - self, - value: Union[ - None, float, int, Tuple[Union[float, int], Union[float, int]], Blur - ], - ): - self.__blur = value - - # shadow - @property - def shadow(self) -> Union[None, BoxShadow, List[BoxShadow]]: - return self.__shadow - - @shadow.setter - def shadow(self, value: Union[None, BoxShadow, List[BoxShadow]]): - self.__shadow = value if value is not None else [] - - # color_filter - @property - def color_filter(self) -> Optional[ColorFilter]: - return self.__color_filter - - @color_filter.setter - def color_filter(self, value: Optional[ColorFilter]): - self.__color_filter = value - - # border - @property - def border(self) -> Optional[Border]: - return self.__border - - @border.setter - def border(self, value: Optional[Border]): - self.__border = value - - # border_radius - @property - def border_radius(self) -> Optional[BorderRadiusValue]: - return self.__border_radius - - @border_radius.setter - def border_radius(self, value: Optional[BorderRadiusValue]): - self.__border_radius = value - - # ignore_interactions - @property - def ignore_interactions(self) -> Optional[bool]: - return self._get_attr("ignoreInteractions", data_type="bool", def_value=False) - - @ignore_interactions.setter - def ignore_interactions(self, value: Optional[str]): - self._set_attr("ignoreInteractions", value) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # shape - @property - def shape(self) -> Optional[BoxShape]: - return self.__shape - - @shape.setter - def shape(self, value: Optional[BoxShape]): - self.__shape = value - self._set_enum_attr("shape", value, BoxShape) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # ink - @property - def ink(self) -> bool: - return self._get_attr("ink", data_type="bool", def_value=False) - - @ink.setter - def ink(self, value: Optional[bool]): - self._set_attr("ink", value) - - # ink color - @property - def ink_color(self) -> Optional[ColorValue]: - return self.__ink_color - - @ink_color.setter - def ink_color(self, value: Optional[ColorValue]): - self.__ink_color = value - self._set_enum_attr("inkColor", value, ColorEnums) - - # animate - @property - def animate(self) -> Optional[AnimationValue]: - return self.__animate - - @animate.setter - def animate(self, value: Optional[AnimationValue]): - self.__animate = value - - # url - @property - def url(self) -> Optional[str]: - return self._get_attr("url") - - @url.setter - def url(self, value: Optional[str]): - self._set_attr("url", value) - - # url_target - @property - def url_target(self) -> Optional[UrlTarget]: - return self.__url_target - - @url_target.setter - def url_target(self, value: Optional[UrlTarget]): - self.__url_target = value - self._set_enum_attr("urlTarget", value, UrlTarget) - - # theme - @property - def theme(self) -> Optional[Theme]: - return self.__theme - - @theme.setter - def theme(self, value: Optional[Theme]): - self.__theme = value - - # dark_theme - @property - def dark_theme(self) -> Optional[Theme]: - return self.__dark_theme - - @dark_theme.setter - def dark_theme(self, value: Optional[Theme]): - self.__dark_theme = value - - # theme_mode - @property - def theme_mode(self) -> Optional[ThemeMode]: - return self.__theme_mode - - @theme_mode.setter - def theme_mode(self, value: Optional[ThemeMode]): - self.__theme_mode = value - self._set_enum_attr("themeMode", value, ThemeMode) - - # on_click - @property - def on_click(self): - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) - self._set_attr("onClick", True if handler is not None else None) - - # on_tap_down - @property - def on_tap_down(self) -> OptionalEventCallable["ContainerTapEvent"]: - return self.__on_tap_down.handler - - @on_tap_down.setter - def on_tap_down(self, handler: OptionalEventCallable["ContainerTapEvent"]): - self.__on_tap_down.handler = handler - self._set_attr("onTapDown", True if handler is not None else None) - - # on_long_press - @property - def on_long_press(self): - return self._get_event_handler("long_press") - - @on_long_press.setter - def on_long_press(self, handler: OptionalControlEventCallable): - self._add_event_handler("long_press", handler) - self._set_attr("onLongPress", True if handler is not None else None) - - # on_hover - @property - def on_hover(self): - return self._get_event_handler("hover") - - @on_hover.setter - def on_hover(self, handler: OptionalControlEventCallable): - self._add_event_handler("hover", handler) - self._set_attr("onHover", True if handler is not None else None) - - -class ContainerTapEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.local_x: float = d.get("lx") - self.local_y: float = d.get("ly") - self.global_x: float = d.get("gx") - self.global_y: float = d.get("gy") diff --git a/sdk/python/packages/flet/src/flet/core/control.py b/sdk/python/packages/flet/src/flet/core/control.py deleted file mode 100644 index f9f13834e..000000000 --- a/sdk/python/packages/flet/src/flet/core/control.py +++ /dev/null @@ -1,610 +0,0 @@ -import datetime as dt -import json -from difflib import SequenceMatcher -from enum import Enum -from typing import ( - TYPE_CHECKING, - Any, - AnyStr, - Dict, - List, - Optional, - Tuple, - Type, - TypeVar, - Union, -) - -from flet.core.badge import Badge, BadgeValue -from flet.core.embed_json_encoder import EmbedJsonEncoder -from flet.core.protocol import Command -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ControlState, - OptionalControlEventCallable, - OptionalNumber, - ResponsiveNumber, - SupportsStr, -) - -if TYPE_CHECKING: - from .page import Page - -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal - -V = TypeVar("V", str, int, float, bool, Any) -DV = TypeVar("DV", bound=Optional[Any]) - - -class Control: - def __init__( - self, - ref: Optional[Ref] = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - ) -> None: - super().__init__() - - self.__page: Optional[Page] = None - self.__attrs: Dict[str, Any] = {} - self.__previous_children = [] - self._id = None - self.__uid: Optional[str] = None - - if ref: - ref.current = self - self.expand = expand - self.expand_loose = expand_loose - self.col = col - self.opacity = opacity - self.tooltip = tooltip - self.badge = badge - self.visible = visible - self.disabled = disabled - self.__data: Any = None - self.data = data - self.rtl = rtl - - self.__event_handlers: Dict[str, OptionalControlEventCallable] = {} - self.parent: Optional[Control] = None - - def is_isolated(self) -> bool: - return False - - def build(self): - pass - - def before_update(self): - pass - - def _before_build_command(self) -> None: - # checking if tooltip has getter/setter in inherited class - if "tooltip" not in vars(self.__class__): - self._set_attr_json("tooltip", self.tooltip) - if isinstance(self.badge, (Badge, str)): - self._set_attr_json("badge", self.badge) - else: - self._set_attr("badge", None) - - self._set_attr_json("col", self.__col) - - def did_mount(self): - pass - - def will_unmount(self): - pass - - def _get_children(self) -> "List[Control]": - return [] - - def _get_control_name(self) -> str: - raise NotImplementedError( - "_get_control_name must be overridden in inherited class" - ) - - def _add_event_handler( - self, event_name: str, handler: OptionalControlEventCallable - ) -> None: - self.__event_handlers[event_name] = handler - - def _get_event_handler(self, event_name: str) -> OptionalControlEventCallable: - return self.__event_handlers.get(event_name) - - def _get_attr( - self, - name: str, - def_value: DV = None, - data_type: Union[ - Literal["string", "int", "float", "bool", "bool?"], AnyStr - ] = "string", - ) -> Union[V, DV]: - name = name.lower() - if name not in self.__attrs: - return def_value - s_val = self.__attrs[name][0] - if data_type == "bool" and s_val is not None and isinstance(s_val, str): - return s_val.lower() == "true" - elif data_type == "bool?" and isinstance(s_val, str): - if s_val.lower() == "true": - return True - elif s_val.lower() == "false": - return False - else: - return def_value - elif data_type == "float" and s_val is not None and isinstance(s_val, str): - return float(s_val) - elif data_type == "int" and s_val is not None and isinstance(s_val, str): - return int(s_val) - else: - return s_val - - def _set_attr(self, name: str, value: V, dirty: bool = True) -> None: - self._set_attr_internal(name, value, dirty) - - def _set_enum_attr( - self, - name: str, - value: V, - enum_type: Union[Type[Enum], Tuple[Type[Enum], ...]], - dirty: bool = True, - ) -> None: - self._set_attr_internal( - name, value.value if isinstance(value, enum_type) else value, dirty - ) - - def _get_value_or_list_attr(self, name: str, delimiter: str) -> Union[List[str], V]: - v = self._get_attr(name) - if v and delimiter in v: - return [x.strip() for x in v.split(delimiter)] - return v - - def _set_value_or_list_attr( - self, name: str, value: Union[List[SupportsStr], V], delimiter: str - ) -> None: - if isinstance(value, List): - value = delimiter.join([str(x) for x in value]) - self._set_attr(name, value) - - def _set_attr_internal(self, name: str, value: V, dirty: bool = True) -> None: - name = name.lower() - orig_val = self.__attrs.get(name) - - if value is None: - if orig_val is None: - return - value = "" - - if orig_val is None or orig_val[0] != value: - self.__attrs[name] = (value, dirty) - - def _set_attr_json(self, name: str, value: V, wrap_attr_dict: bool = False) -> None: - ov = self._get_attr(name) - nv = self._convert_attr_json( - self._wrap_attr_dict(value) if wrap_attr_dict else value - ) - if ov != nv: - self._set_attr(name, nv) - - def _convert_attr_json(self, value: V) -> Optional[str]: - return ( - json.dumps(value, cls=EmbedJsonEncoder, separators=(",", ":")) - if value is not None - else None - ) - - def _wrap_attr_dict(self, value: Optional[Union[Dict, Any]]) -> Optional[Dict]: - if value is None or isinstance(value, Dict): - return value - return {ControlState.DEFAULT: value} - - # event_handlers - @property - def event_handlers(self) -> Dict[str, Any]: - return self.__event_handlers - - # _previous_children - @property - def _previous_children(self): - return self.__previous_children - - # _id - @property - def _id(self) -> str: - return self._get_attr("id") - - @_id.setter - def _id(self, value: str): - self._set_attr("id", value) - - # page - @property - def page(self) -> "Optional[Page]": - return self.__page - - @page.setter - def page(self, page: "Optional[Page]"): - self.__page = page - - # uid - @property - def uid(self) -> Optional[str]: - return self.__uid - - # expand - @property - def expand(self) -> Optional[Union[bool, int]]: - return self.__expand - - @expand.setter - def expand(self, value: Optional[Union[bool, int]]): - self.__expand = value - if value and isinstance(value, bool): - value = 1 - self._set_attr("expand", value if value else None) - - # expand_loose - @property - def expand_loose(self) -> bool: - return self._get_attr("expandLoose", data_type="bool", def_value=False) - - @expand_loose.setter - def expand_loose(self, value: Optional[bool]): - self._set_attr("expandLoose", value) - - # rtl - @property - def rtl(self) -> bool: - return self._get_attr("rtl", data_type="bool", def_value=False) - - @rtl.setter - def rtl(self, value: Optional[bool]): - self._set_attr("rtl", value) - - # col - @property - def col(self) -> Optional[ResponsiveNumber]: - return self.__col - - @col.setter - def col(self, value: Optional[ResponsiveNumber]): - self.__col = value - - # opacity - @property - def opacity(self) -> float: - return self._get_attr("opacity", data_type="float", def_value=1.0) - - @opacity.setter - def opacity(self, value: OptionalNumber): - assert ( - value is None or 0.0 <= value <= 1.0 - ), "opacity must be between 0.0 and 1.0" - self._set_attr("opacity", value) - - # visible - @property - def visible(self) -> bool: - return self._get_attr("visible", data_type="bool", def_value=True) - - @visible.setter - def visible(self, value: Optional[bool]): - self._set_attr("visible", value) - - # disabled - @property - def disabled(self) -> bool: - return self._get_attr("disabled", data_type="bool", def_value=False) - - @disabled.setter - def disabled(self, value: Optional[bool]): - self._set_attr("disabled", value) - - # data - @property - def data(self) -> Optional[Any]: - return self.__data - - @data.setter - def data(self, value: Optional[Any]): - self.__data = value - - # public methods - def update(self) -> None: - assert ( - self.__page - ), f"{self.__class__.__qualname__} Control must be added to the page first" - self.__page.update(self) - - def clean(self) -> None: - assert ( - self.__page - ), f"{self.__class__.__qualname__} Control must be added to the page" - self.__page._clean(self) - - def invoke_method( - self, - method_name: str, - arguments: Optional[Dict[str, str]] = None, - wait_for_result: bool = False, - wait_timeout: Optional[float] = 5, - ) -> Optional[str]: - assert ( - self.__page - ), f"{self.__class__.__qualname__} Control must be added to the page first" - if arguments: - # remove items with None values and convert other values to string - arguments = {k: str(v) for k, v in arguments.items() if v is not None} - return self.__page._invoke_method( - control_id=self.uid, - method_name=method_name, - arguments=arguments, - wait_for_result=wait_for_result, - wait_timeout=wait_timeout, - ) - - def invoke_method_async( - self, - method_name: str, - arguments: Optional[Dict[str, str]] = None, - wait_for_result: bool = False, - wait_timeout: Optional[float] = 5, - ): - assert ( - self.__page - ), f"{self.__class__.__qualname__} Control must be added to the page first" - if arguments: - # remove items with None values and convert other values to string - arguments = {k: str(v) for k, v in arguments.items() if v is not None} - return self.__page._invoke_method_async( - control_id=self.uid, - method_name=method_name, - arguments=arguments, - wait_for_result=wait_for_result, - wait_timeout=wait_timeout, - ) - - def copy_attrs(self, dest: Dict[str, Any]) -> None: - for attr_name in sorted(self.__attrs): - attr_name_lower = attr_name.lower() - value, dirty = self.__attrs[attr_name_lower] - - if dirty or value is None: - continue - - if isinstance(value, bool): - sval = str(value).lower() - elif isinstance(value, (dt.datetime, dt.date)): - sval = value.isoformat() - else: - sval = str(value) - - dest[attr_name_lower] = sval - - def build_update_commands( - self, index, commands, added_controls, removed_controls, isolated: bool = False - ) -> None: - update_cmd = self._build_command(update=True) - - if len(update_cmd.attrs) > 0: - update_cmd.name = "set" - commands.append(update_cmd) - if isolated: - return - # go through children - - previous_children = self.__previous_children - current_children = self._get_children() - - hashes = {} - previous_ints = [] - current_ints = [] - - for ctrl in previous_children: - hashes[hash(ctrl)] = ctrl - previous_ints.append(hash(ctrl)) - for ctrl in current_children: - hashes[hash(ctrl)] = ctrl - current_ints.append(hash(ctrl)) - sm = SequenceMatcher(None, previous_ints, current_ints) - - n = 0 - for tag, a1, a2, b1, b2 in sm.get_opcodes(): - if tag == "delete" or tag == "replace": - # deleted controls - - ids = [] - for h in previous_ints[a1:a2]: - ctrl = hashes[h] - # check if re-added control is being deleted - # which means it's a replace - - i = 0 - replaced = False - while i < len(commands): - cmd = commands[i] - if cmd.name == "add" and any( - c for c in cmd.commands if c.attrs.get("id") == ctrl.__uid - ): - # insert delete command before add - - commands.insert(i, Command(0, "remove", [ctrl.__uid])) - replaced = True - break - i += 1 - removed_controls.extend( - self._remove_control_recursively(index, ctrl) - ) - if not replaced: - ids.append(ctrl.__uid) - if len(ids) > 0: - commands.append(Command(0, "remove", ids)) - if tag == "replace": - # add - - for h in current_ints[b1:b2]: - ctrl = hashes[h] - innerCmds = ctrl._build_add_commands( - index=index, added_controls=added_controls - ) - assert self.__uid is not None - ctrl.parent = self # set as parent - commands.append( - Command( - indent=0, - name="add", - attrs={"to": self.__uid, "at": str(n)}, - commands=innerCmds, - ) - ) - n += 1 - elif tag == "equal": - # unchanged control - - for h in previous_ints[a1:a2]: - ctrl = hashes[h] - ctrl.build_update_commands( - index, - commands, - added_controls, - removed_controls, - isolated=ctrl.is_isolated(), - ) - n += 1 - elif tag == "insert": - # add - - for h in current_ints[b1:b2]: - ctrl = hashes[h] - innerCmds = ctrl._build_add_commands( - index=index, added_controls=added_controls - ) - assert self.__uid is not None - ctrl.parent = self # set as parent - commands.append( - Command( - indent=0, - name="add", - attrs={"to": self.__uid, "at": str(n)}, - commands=innerCmds, - ) - ) - n += 1 - self.__previous_children.clear() - self.__previous_children.extend(current_children) - - def _remove_control_recursively(self, index, control: "Control") -> "List[Control]": - removed_controls = [] - - if control.__uid in index: - del index[control.__uid] - - for child in control._get_children(): - removed_controls.extend(self._remove_control_recursively(index, child)) - for child in control._previous_children: - removed_controls.extend(self._remove_control_recursively(index, child)) - removed_controls.append(control) - return removed_controls - - # private methods - def _build_add_commands( - self, indent: int = 0, index=None, added_controls=None - ) -> List[Command]: - if index: - self.page = index["page"] - self.build() - - # remove control from index - if self.__uid and index is not None and self.__uid in index: - del index[self.__uid] - commands = [] - - # main command - command = self._build_command(False) - command.indent = indent - command.values.append(self._get_control_name()) - commands.append(command) - - if added_controls is not None: - added_controls.append(self) - # controls - - children = self._get_children() - for control in children: - childCmd = control._build_add_commands( - indent=indent + 2, index=index, added_controls=added_controls - ) - commands.extend(childCmd) - control.parent = self # set as parent - self.__previous_children.clear() - self.__previous_children.extend(children) - - return commands - - def _build_command(self, update: bool = False) -> Command: - command = Command(0, None, [], {}, []) - - if update and not self.__uid: - return command - self._before_build_command() - self.before_update() - - for attrName in sorted(self.__attrs): - attrName = attrName.lower() - dirty = self.__attrs[attrName][1] - - if (update and not dirty) or attrName == "id": - continue - val = self.__attrs[attrName][0] - sval = "" - if val is None: - continue - elif isinstance(val, bool): - sval = str(val).lower() - elif isinstance(val, dt.datetime) or isinstance(val, dt.date): - sval = val.isoformat() - else: - sval = str(val) - command.attrs[attrName] = sval - self.__attrs[attrName] = (val, False) - id = self.__attrs.get("id") - if not update and self.__uid is not None: - command.attrs["id"] = self.__uid - elif not update and id is not None: - command.attrs["id"] = id - elif update and len(command.attrs) > 0: - assert self.__uid is not None - command.values.append(self.__uid) - return command - - def _dispose(self) -> None: - self.page = None - self.__event_handlers.clear() - - # Magic methods - def __str__(self) -> str: - attrs = {} - for k, v in self.__attrs.items(): - attrs[k] = v[0] - return f"{self._get_control_name()} {attrs}" - - def __repr__(self) -> str: - return ( - f"{self.__class__.__name__}(" - + ", ".join( - f"{k}={v[0]}" if not isinstance(v[0], str) else f"{k}='{v[0]}'" - for k, v in self.__attrs.items() - ) - + ")" - ) diff --git a/sdk/python/packages/flet/src/flet/core/control_event.py b/sdk/python/packages/flet/src/flet/core/control_event.py deleted file mode 100644 index 0532208a9..000000000 --- a/sdk/python/packages/flet/src/flet/core/control_event.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import Optional - -from flet.core.event import Event - - -class ControlEvent(Event): - def __init__(self, target: str, name: str, data: Optional[str], control, page): - Event.__init__(self, target=target, name=name, data=data) - - self.control = control - self.page = page diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_action_sheet.py b/sdk/python/packages/flet/src/flet/core/cupertino_action_sheet.py deleted file mode 100644 index d717d8be1..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_action_sheet.py +++ /dev/null @@ -1,155 +0,0 @@ -from typing import Any, List, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class CupertinoActionSheet(ConstrainedControl): - """ - An iOS-style action sheet. - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinoactionsheet - """ - - def __init__( - self, - title: Optional[Control] = None, - message: Optional[Control] = None, - actions: Optional[List[Control]] = None, - cancel: Optional[Control] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.cancel = cancel - self.title = title - self.message = message - self.actions = actions - - def _get_control_name(self): - return "cupertinoactionsheet" - - def _get_children(self): - children = [] - if self.__cancel: - self.__cancel._set_attr_internal("n", "cancel") - children.append(self.__cancel) - if self.__title: - self.__title._set_attr_internal("n", "title") - children.append(self.__title) - if self.__message: - self.__message._set_attr_internal("n", "message") - children.append(self.__message) - for action in self.__actions: - action._set_attr_internal("n", "action") - children.append(action) - return children - - # cancel - @property - def cancel(self) -> Optional[Control]: - return self.__cancel - - @cancel.setter - def cancel(self, value: Optional[Control]): - self.__cancel = value - - # title - @property - def title(self) -> Optional[Control]: - return self.__title - - @title.setter - def title(self, value: Optional[Control]): - self.__title = value - - # message - @property - def message(self) -> Optional[Control]: - return self.__message - - @message.setter - def message(self, value: Optional[Control]): - self.__message = value - - # actions - @property - def actions(self) -> List[Control]: - return self.__actions - - @actions.setter - def actions(self, value: Optional[List[Control]]): - self.__actions = value if value is not None else [] diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_action_sheet_action.py b/sdk/python/packages/flet/src/flet/core/cupertino_action_sheet_action.py deleted file mode 100644 index 1fabf455b..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_action_sheet_action.py +++ /dev/null @@ -1,175 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - MouseCursor, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class CupertinoActionSheetAction(ConstrainedControl): - """ - An action button typically used in a CupertinoActionSheet. - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinoactionsheetaction - """ - - def __init__( - self, - text: Optional[str] = None, - content: Optional[Control] = None, - is_default_action: Optional[bool] = None, - is_destructive_action: Optional[bool] = None, - mouse_cursor: Optional[MouseCursor] = None, - on_click: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.text = text - self.content = content - self.is_default_action = is_default_action - self.is_destructive_action = is_destructive_action - self.mouse_cursor = mouse_cursor - self.on_click = on_click - - def _get_control_name(self): - return "cupertinoactionsheetaction" - - def _get_children(self): - if self.__content is not None: - self.__content._set_attr_internal("n", "content") - return [self.__content] - return [] - - def before_update(self): - super().before_update() - assert self.text is not None or ( - (self.__content is not None and self.__content.visible) - ), "either text or (visible) content must be provided visible" - - # text - @property - def text(self) -> Optional[str]: - return self._get_attr("text") - - @text.setter - def text(self, value: Optional[str]): - self._set_attr("text", value) - - # is_default_action - @property - def is_default_action(self) -> bool: - return self._get_attr("isDefaultAction", data_type="bool", def_value=False) - - @is_default_action.setter - def is_default_action(self, value: Optional[bool]): - self._set_attr("isDefaultAction", value) - - # is_destructive_action - @property - def is_destructive_action(self) -> bool: - return self._get_attr("isDestructiveAction", data_type="bool", def_value=False) - - @is_destructive_action.setter - def is_destructive_action(self, value: Optional[bool]): - self._set_attr("isDestructiveAction", value) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self.__mouse_cursor - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self.__mouse_cursor = value - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_activity_indicator.py b/sdk/python/packages/flet/src/flet/core/cupertino_activity_indicator.py deleted file mode 100644 index 41a7f3ba8..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_activity_indicator.py +++ /dev/null @@ -1,119 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class CupertinoActivityIndicator(ConstrainedControl): - """ - An iOS-style activity indicator that spins clockwise. - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinoactivityindicator - """ - - def __init__( - self, - radius: OptionalNumber = None, - color: Optional[ColorValue] = None, - animating: Optional[bool] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.color = color - self.radius = radius - self.animating = animating - - def _get_control_name(self): - return "cupertinoactivityindicator" - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # animating - @property - def animating(self) -> bool: - return self._get_attr("animating", data_type="bool", def_value=True) - - @animating.setter - def animating(self, value: Optional[bool]): - self._set_attr("animating", value) - - # radius - @property - def radius(self) -> OptionalNumber: - return self._get_attr("radius", data_type="float") - - @radius.setter - def radius(self, value: OptionalNumber): - self._set_attr("radius", value) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_alert_dialog.py b/sdk/python/packages/flet/src/flet/core/cupertino_alert_dialog.py deleted file mode 100644 index 9ae891989..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_alert_dialog.py +++ /dev/null @@ -1,214 +0,0 @@ -from typing import Any, List, Optional - -from flet.core.animation import Animation -from flet.core.control import Control -from flet.core.ref import Ref -from flet.core.types import OptionalControlEventCallable - - -class CupertinoAlertDialog(Control): - """ - An iOS-style alert dialog. - An alert dialog informs the user about situations that require acknowledgement. An alert dialog has an optional title and an optional list of actions. The title is displayed above the content and the actions are displayed below the content. - - Example: - ``` - import flet as ft - - - def main(page: ft.Page): - page.horizontal_alignment = ft.CrossAxisAlignment.CENTER - page.scroll = True - - def handle_action_click(e): - page.add(ft.Text(f"Action clicked: {e.control.text}")) - # e.control is the clicked action button, e.control.parent is the corresponding parent dialog of the button - page.close(e.control.parent) - - cupertino_actions = [ - ft.CupertinoDialogAction( - "Yes", - is_destructive_action=True, - on_click=handle_action_click, - ), - ft.CupertinoDialogAction( - text="No", - is_default_action=False, - on_click=handle_action_click, - ), - ] - - material_actions = [ - ft.TextButton(text="Yes", on_click=handle_action_click), - ft.TextButton(text="No", on_click=handle_action_click), - ] - - page.add( - ft.FilledButton( - text="Open Material Dialog", - on_click=lambda e: page.open( - ft.AlertDialog( - title=ft.Text("Material Alert Dialog"), - content=ft.Text("Do you want to delete this file?"), - actions=material_actions, - ) - ), - ), - ft.CupertinoFilledButton( - text="Open Cupertino Dialog", - on_click=lambda e: page.open( - ft.CupertinoAlertDialog( - title=ft.Text("Cupertino Alert Dialog"), - content=ft.Text("Do you want to delete this file?"), - actions=cupertino_actions, - ) - ), - ), - ft.FilledButton( - text="Open Adaptive Dialog", - adaptive=True, - on_click=lambda e: page.open( - ft.AlertDialog( - adaptive=True, - title=ft.Text("Adaptive Alert Dialog"), - content=ft.Text("Do you want to delete this file?"), - actions=cupertino_actions if page.platform in [ft.PagePlatform.IOS, ft.PagePlatform.MACOS] else material_actions, - ) - ), - ), - ) - - - ft.app(target=main) - ``` - ----- - - Online docs: https://flet.dev/docs/controls/cupertinoalertdialog - """ - - def __init__( - self, - open: bool = False, - modal: bool = False, - title: Optional[Control] = None, - content: Optional[Control] = None, - actions: Optional[List[Control]] = None, - inset_animation: Optional[Animation] = None, - on_dismiss: OptionalControlEventCallable = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - barrier_color: Optional[str] = None, - ): - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - self.open = open - self.modal = modal - self.title = title - self.content = content - self.actions = actions - self.on_dismiss = on_dismiss - self.barrier_color = barrier_color - self.inset_animation = inset_animation - - def _get_control_name(self): - return "cupertinoalertdialog" - - def before_update(self): - super().before_update() - self._set_attr_json("insetAnimation", self.__inset_animation) - - def _get_children(self): - children = [] - if self.__title: - self.__title._set_attr_internal("n", "title") - children.append(self.__title) - if self.__content: - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - for action in self.__actions: - action._set_attr_internal("n", "action") - children.append(action) - return children - - # open - @property - def open(self) -> bool: - return self._get_attr("open", data_type="bool", def_value=False) - - @open.setter - def open(self, value: Optional[bool]): - self._set_attr("open", value) - - # modal - @property - def modal(self) -> bool: - return self._get_attr("modal", data_type="bool", def_value=False) - - @modal.setter - def modal(self, value: Optional[bool]): - self._set_attr("modal", value) - - # title - @property - def title(self) -> Optional[Control]: - return self.__title - - @title.setter - def title(self, value: Optional[Control]): - self.__title = value - - # inset_animation - @property - def inset_animation(self) -> Optional[Animation]: - return self.__inset_animation - - @inset_animation.setter - def inset_animation(self, value: Optional[Animation]): - self.__inset_animation = value - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # barrier_color - @property - def barrier_color(self) -> Optional[str]: - return self._get_attr("barrierColor") - - @barrier_color.setter - def barrier_color(self, value: Optional[str]): - self._set_attr("barrierColor", value) - - # actions - @property - def actions(self) -> List[Control]: - return self.__actions - - @actions.setter - def actions(self, value: Optional[List[Control]]): - self.__actions = value if value is not None else [] - - # on_dismiss - @property - def on_dismiss(self) -> OptionalControlEventCallable: - return self._get_event_handler("dismiss") - - @on_dismiss.setter - def on_dismiss(self, handler: OptionalControlEventCallable): - self._add_event_handler("dismiss", handler) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_app_bar.py b/sdk/python/packages/flet/src/flet/core/cupertino_app_bar.py deleted file mode 100644 index 61a3d23f9..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_app_bar.py +++ /dev/null @@ -1,293 +0,0 @@ -from typing import Any, Optional - -from flet.core.border import Border -from flet.core.control import Control -from flet.core.ref import Ref -from flet.core.types import Brightness, ColorEnums, ColorValue, PaddingValue -from flet.utils.deprecated import deprecated_property - - -class CupertinoAppBar(Control): - """ - An iOS-styled application bar. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.theme_mode = ft.ThemeMode.LIGHT - - page.appbar = ft.CupertinoAppBar( - leading=ft.Icon(ft.icons.PALETTE), - bgcolor=ft.colors.SURFACE_VARIANT, - trailing=ft.Icon(ft.icons.WB_SUNNY_OUTLINED), - middle=ft.Text("AppBar Example"), - ) - page.add(ft.Text("Body!")) - - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinoappbar - """ - - def __init__( - self, - leading: Optional[Control] = None, - middle: Optional[Control] = None, - title: Optional[Control] = None, - trailing: Optional[Control] = None, - bgcolor: Optional[ColorValue] = None, - automatically_imply_leading: Optional[bool] = None, - automatically_imply_middle: Optional[bool] = None, - automatically_imply_title: Optional[bool] = None, - border: Optional[Border] = None, - padding: Optional[PaddingValue] = None, - transition_between_routes: Optional[bool] = None, - previous_page_title: Optional[str] = None, - brightness: Optional[Brightness] = None, - automatic_background_visibility: Optional[bool] = None, - enable_background_filter_blur: Optional[bool] = None, - large: Optional[bool] = None, - # - # Control - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - ): - Control.__init__( - self, ref=ref, visible=visible, disabled=disabled, data=data, rtl=rtl - ) - - self.leading = leading - self.middle = middle - self.title = title - self.automatically_imply_leading = automatically_imply_leading - self.automatically_imply_middle = automatically_imply_middle - self.automatically_imply_title = automatically_imply_title - self.border = border - self.padding = padding - self.trailing = trailing - self.transition_between_routes = transition_between_routes - self.previous_page_title = previous_page_title - self.bgcolor = bgcolor - self.brightness = brightness - self.automatic_background_visibility = automatic_background_visibility - self.enable_background_filter_blur = enable_background_filter_blur - self.large = large - - def _get_control_name(self): - return "cupertinoappbar" - - def before_update(self): - super().before_update() - self._set_attr_json("border", self.__border) - self._set_attr_json("padding", self.__padding) - - def _get_children(self): - children = [] - if self.__leading: - self.__leading._set_attr_internal("n", "leading") - children.append(self.__leading) - if self.__title or self.__middle: - t = self.__title or self.__middle - t._set_attr_internal("n", "title") - children.append(t) - if self.__trailing: - self.__trailing._set_attr_internal("n", "trailing") - children.append(self.__trailing) - return children - - # leading - @property - def leading(self) -> Optional[Control]: - return self.__leading - - @leading.setter - def leading(self, value: Optional[Control]): - """ - A Control to display before the toolbar's title. - - Typically the leading control is an Icon or an IconButton. - """ - self.__leading = value - - # middle - @property - def middle(self) -> Optional[Control]: - deprecated_property( - name="middle", - version="0.27.0", - delete_version="0.30.0", - reason="Use title instead.", - ) - return self.__middle - - @middle.setter - def middle(self, value: Optional[Control]): - self.__middle = value - if value is not None: - deprecated_property( - name="middle", - version="0.27.0", - delete_version="0.30.0", - reason="Use title instead.", - ) - - # title - @property - def title(self) -> Optional[Control]: - return self.__title - - @title.setter - def title(self, value: Optional[Control]): - self.__title = value - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # automatically_imply_leading - @property - def automatically_imply_leading(self) -> bool: - return self._get_attr( - "automaticallyImplyLeading", data_type="bool", def_value=True - ) - - @automatically_imply_leading.setter - def automatically_imply_leading(self, value: Optional[bool]): - self._set_attr("automaticallyImplyLeading", value) - - # automatically_imply_middle - @property - def automatically_imply_middle(self) -> bool: - deprecated_property( - name="automatically_imply_middle", - version="0.27.0", - delete_version="0.30.0", - reason="Use automatically_imply_title instead.", - ) - return self._get_attr( - "automaticallyImplyMiddle", data_type="bool", def_value=True - ) - - @automatically_imply_middle.setter - def automatically_imply_middle(self, value: Optional[bool]): - self._set_attr("automaticallyImplyMiddle", value) - if value is not None: - deprecated_property( - name="automatically_imply_middle", - version="0.27.0", - delete_version="0.30.0", - reason="Use automatically_imply_title instead.", - ) - - # automatically_imply_title - @property - def automatically_imply_title(self) -> bool: - return self._get_attr( - "automaticallyImplyTitle", data_type="bool", def_value=True - ) - - @automatically_imply_title.setter - def automatically_imply_title(self, value: Optional[bool]): - self._set_attr("automaticallyImplyTitle", value) - - # large - @property - def large(self) -> bool: - return self._get_attr("large", data_type="bool", def_value=False) - - @large.setter - def large(self, value: Optional[bool]): - self._set_attr("large", value) - - # border - @property - def border(self) -> Optional[Border]: - return self.__border - - @border.setter - def border(self, value: Optional[Border]): - self.__border = value - - # trailing - @property - def trailing(self) -> Optional[Control]: - return self.__trailing - - @trailing.setter - def trailing(self, value: Optional[Control]): - self.__trailing = value - - # transition_between_routes - @property - def transition_between_routes(self) -> bool: - return self._get_attr( - "transitionBetweenRoutes", data_type="bool", def_value=True - ) - - @transition_between_routes.setter - def transition_between_routes(self, value: Optional[bool]): - self._set_attr("transitionBetweenRoutes", value) - - # previous_page_title - @property - def previous_page_title(self): - return self._get_attr("previousPageTitle") - - @previous_page_title.setter - def previous_page_title(self, value): - self._set_attr("previousPageTitle", value) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # brightness - @property - def brightness(self) -> Optional[Brightness]: - return self.__brightness - - @brightness.setter - def brightness(self, value: Optional[Brightness]): - self.__brightness = value - self._set_enum_attr("brightness", value, Brightness) - - # automatic_background_visibility - @property - def automatic_background_visibility(self) -> Optional[bool]: - return self._get_attr( - "automaticBackgroundVisibility", data_type="bool", def_value=True - ) - - @automatic_background_visibility.setter - def automatic_background_visibility(self, value: Optional[bool]): - self._set_attr("automaticBackgroundVisibility", value) - - # enable_background_filter_blur - @property - def enable_background_filter_blur(self) -> Optional[bool]: - return self._get_attr("backgroundFilterBlur", data_type="bool", def_value=True) - - @enable_background_filter_blur.setter - def enable_background_filter_blur(self, value: Optional[bool]): - self._set_attr("backgroundFilterBlur", value) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_bottom_sheet.py b/sdk/python/packages/flet/src/flet/core/cupertino_bottom_sheet.py deleted file mode 100644 index c8bc6e328..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_bottom_sheet.py +++ /dev/null @@ -1,133 +0,0 @@ -from typing import Any, Optional - -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.types import ( - ColorEnums, - ColorValue, - OptionalControlEventCallable, - PaddingValue, -) - - -class CupertinoBottomSheet(Control): - """ - A Cupertino version of modal bottom sheet. - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinobottomsheet - """ - - def __init__( - self, - content: Optional[Control] = None, - open: bool = False, - modal: bool = False, - bgcolor: Optional[ColorValue] = None, - height: OptionalNumber = None, - padding: Optional[PaddingValue] = None, - on_dismiss: OptionalControlEventCallable = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - self.__content: Optional[Control] = None - - self.open = open - self.modal = modal - self.bgcolor = bgcolor - self.height = height - self.padding = padding - self.content = content - self.on_dismiss = on_dismiss - - def _get_control_name(self): - return "cupertinobottomsheet" - - def before_update(self): - super().before_update() - self._set_attr_json("padding", self.__padding) - - def _get_children(self): - children = [] - if self.__content: - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - return children - - # open - @property - def open(self) -> bool: - return self._get_attr("open", data_type="bool", def_value=False) - - @open.setter - def open(self, value: Optional[bool]): - self._set_attr("open", value) - - # modal - @property - def modal(self) -> bool: - return self._get_attr("modal", data_type="bool", def_value=False) - - @modal.setter - def modal(self, value: Optional[bool]): - self._set_attr("modal", value) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgColor", value, ColorEnums) - - # height - @property - def height(self) -> float: - return self._get_attr("height", data_type="float", def_value=220.0) - - @height.setter - def height(self, value: OptionalNumber): - self._set_attr("height", value) - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # content - @property - def content(self): - return self.__content - - @content.setter - def content(self, value): - self.__content = value - - # on_dismiss - @property - def on_dismiss(self) -> OptionalControlEventCallable: - return self._get_event_handler("dismiss") - - @on_dismiss.setter - def on_dismiss(self, handler: OptionalControlEventCallable): - self._add_event_handler("dismiss", handler) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_button.py b/sdk/python/packages/flet/src/flet/core/cupertino_button.py deleted file mode 100644 index dae41a361..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_button.py +++ /dev/null @@ -1,348 +0,0 @@ -import warnings -from typing import Any, Optional, Union - -from flet.core.alignment import Alignment -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - BorderRadiusValue, - ColorEnums, - ColorValue, - IconEnums, - IconValue, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, - UrlTarget, -) - - -class CupertinoButton(ConstrainedControl): - """ - An iOS-style button. - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinobutton - """ - - def __init__( - self, - text: Optional[str] = None, - icon: Optional[IconValue] = None, - icon_color: Optional[ColorValue] = None, - content: Optional[Control] = None, - bgcolor: Optional[ColorValue] = None, - color: Optional[ColorValue] = None, - disabled_bgcolor: Optional[ColorValue] = None, - opacity_on_click: OptionalNumber = None, - min_size: OptionalNumber = None, - padding: Optional[PaddingValue] = None, - alignment: Optional[Alignment] = None, - border_radius: Optional[BorderRadiusValue] = None, - url: Optional[str] = None, - url_target: Optional[UrlTarget] = None, - autofocus: Optional[bool] = None, - focus_color: Optional[bool] = None, - on_click: OptionalControlEventCallable = None, - on_long_press: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.disabled_bgcolor = disabled_bgcolor - self.text = text - self.icon = icon - self.icon_color = icon_color - self.bgcolor = bgcolor - self.color = color - self.border_radius = border_radius - self.min_size = min_size - self.opacity_on_click = opacity_on_click - self.padding = padding - self.alignment = alignment - self.content = content - self.url = url - self.url_target = url_target - self.on_click = on_click - self.on_long_press = on_long_press - self.on_focus = on_focus - self.on_blur = on_blur - self.autofocus = autofocus - self.focus_color = focus_color - - def _get_control_name(self): - return "cupertinobutton" - - def before_update(self): - super().before_update() - assert ( - self.text or self.icon or (self.__content and self.__content.visible) - ), "at minimum, text, icon or a visible content must be provided" - self._set_attr_json("padding", self.__padding) - self._set_attr_json("borderRadius", self.__border_radius) - self._set_attr_json("alignment", self.__alignment) - - def _get_children(self): - if self.__content is None: - return [] - self.__content._set_attr_internal("n", "content") - return [self.__content] - - # text - @property - def text(self): - return self._get_attr("text") - - @text.setter - def text(self, value): - self._set_attr("text", value) - - # focus_color - @property - def focus_color(self): - return self.__focus_color - - @focus_color.setter - def focus_color(self, value): - self.__focus_color = value - self._set_enum_attr("focusColor", value, ColorEnums) - - # autofocus - @property - def autofocus(self): - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value): - self._set_attr("autofocus", value) - - # icon - @property - def icon(self): - return self.__icon - - @icon.setter - def icon(self, value): - self.__icon = value - self._set_enum_attr("icon", value, IconEnums) - - # icon_color - @property - def icon_color(self): - return self.__icon_color - - @icon_color.setter - def icon_color(self, value): - self.__icon_color = value - self._set_enum_attr("iconColor", value, ColorEnums) - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value - - # disabled_bgcolor - @property - def disabled_bgcolor(self) -> Optional[str]: - return self.__disabled_bgcolor - - @disabled_bgcolor.setter - def disabled_bgcolor(self, value: Optional[str]): - self.__disabled_bgcolor = value - self._set_enum_attr("disabledBgcolor", value, ColorEnums) - - # opacity_on_click - @property - def opacity_on_click(self) -> float: - return self._get_attr("opacityOnClick", data_type="float", def_value=0.4) - - @opacity_on_click.setter - def opacity_on_click(self, value: OptionalNumber): - if value is not None: - value = max(0.0, min(value, 1.0)) # make sure 0.0 <= value <= 1.0 - self._set_attr("opacityOnClick", value) - - # border_radius - @property - def border_radius(self) -> Optional[BorderRadiusValue]: - return self.__border_radius - - @border_radius.setter - def border_radius(self, value: Optional[BorderRadiusValue]): - self.__border_radius = value - - # min_size - @property - def min_size(self) -> float: - return self._get_attr("minSize", data_type="float", def_value=44.0) - - @min_size.setter - def min_size(self, value: OptionalNumber): - self._set_attr("minSize", value) - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgColor", value, ColorEnums) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # url - @property - def url(self): - return self._get_attr("url") - - @url.setter - def url(self, value): - self._set_attr("url", value) - - # url_target - @property - def url_target(self) -> Optional[UrlTarget]: - return self.__url_target - - @url_target.setter - def url_target(self, value: Optional[UrlTarget]): - self.__url_target = value - self._set_enum_attr("urlTarget", value, UrlTarget) - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # on_long_press - @property - def on_long_press(self) -> OptionalControlEventCallable: - return self._get_event_handler("longPress") - - @on_long_press.setter - def on_long_press(self, handler: OptionalControlEventCallable): - self._add_event_handler("longPress", handler) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_checkbox.py b/sdk/python/packages/flet/src/flet/core/cupertino_checkbox.py deleted file mode 100644 index 7b211151d..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_checkbox.py +++ /dev/null @@ -1,305 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.border import BorderSide -from flet.core.buttons import OutlinedBorder -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - ControlStateValue, - LabelPosition, - MouseCursor, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class CupertinoCheckbox(ConstrainedControl): - """ - A macOS style checkbox. Checkbox allows to select one or more items from a group, or switch between two mutually exclusive options (checked or unchecked, on or off). - - Example: - ``` - import flet as ft - - def main(page): - c = ft.CupertinoCheckbox( - label="Cupertino Checkbox", - active_color=ft.colors.GREEN, - inactive_color=ft.colors.RED, - check_color=ft.colors.BLUE, - ), - page.add(c) - - ft.app(target=main) - ``` - - ----- - Online docs: https://flet.dev/docs/controls/cupertinocheckbox - """ - - def __init__( - self, - label: Optional[str] = None, - label_position: Optional[LabelPosition] = None, - value: Optional[bool] = None, - tristate: Optional[bool] = None, - autofocus: Optional[bool] = None, - check_color: Optional[ColorValue] = None, - active_color: Optional[ColorValue] = None, - focus_color: Optional[ColorValue] = None, - fill_color: ControlStateValue[ColorValue] = None, - shape: Optional[OutlinedBorder] = None, - mouse_cursor: Optional[MouseCursor] = None, - semantics_label: Optional[str] = None, - border_side: ControlStateValue[BorderSide] = None, - on_change: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - self.value = value - self.tristate = tristate - self.label = label - self.label_position = label_position - self.autofocus = autofocus - self.check_color = check_color - self.active_color = active_color - self.focus_color = focus_color - self.on_change = on_change - self.on_focus = on_focus - self.on_blur = on_blur - self.shape = shape - self.mouse_cursor = mouse_cursor - self.semantics_label = semantics_label - self.border_side = border_side - self.fill_color = fill_color - - def _get_control_name(self): - return "cupertinocheckbox" - - def before_update(self): - super().before_update() - self._set_attr_json("shape", self.__shape) - self._set_attr_json("borderSide", self.__border_side, wrap_attr_dict=True) - self._set_attr_json("fillColor", self.__fill_color, wrap_attr_dict=True) - - # value - @property - def value(self) -> Optional[bool]: - return self._get_attr( - "value", data_type="bool?", def_value=False if not self.tristate else None - ) - - @value.setter - def value(self, value: Optional[bool]): - self._set_attr("value", value) - - # tristate - @property - def tristate(self) -> bool: - return self._get_attr("tristate", data_type="bool", def_value=False) - - @tristate.setter - def tristate(self, value: Optional[bool]): - self._set_attr("tristate", value) - - # label - @property - def label(self) -> Optional[str]: - return self._get_attr("label") - - @label.setter - def label(self, value: Optional[str]): - self._set_attr("label", value) - - # label_position - @property - def label_position(self) -> Optional[LabelPosition]: - return self.__label_position - - @label_position.setter - def label_position(self, value: Optional[LabelPosition]): - self.__label_position = value - self._set_enum_attr("labelPosition", value, LabelPosition) - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # check_color - @property - def check_color(self) -> Optional[ColorValue]: - return self.__check_color - - @check_color.setter - def check_color(self, value: Optional[ColorValue]): - self.__check_color = value - self._set_enum_attr("checkColor", value, ColorEnums) - - # active_color - @property - def active_color(self) -> Optional[ColorValue]: - return self.__active_color - - @active_color.setter - def active_color(self, value: Optional[ColorValue]): - self.__active_color = value - self._set_enum_attr("activeColor", value, ColorEnums) - - # focus_color - @property - def focus_color(self) -> Optional[ColorValue]: - return self.__focus_color - - @focus_color.setter - def focus_color(self, value: Optional[ColorValue]): - self.__focus_color = value - self._set_enum_attr("focusColor", value, ColorEnums) - - # fill_color - @property - def fill_color(self) -> ControlStateValue[ColorValue]: - return self.__fill_color - - @fill_color.setter - def fill_color(self, value: ControlStateValue[ColorValue]): - self.__fill_color = value - - # shape - @property - def shape(self) -> Optional[OutlinedBorder]: - return self.__shape - - @shape.setter - def shape(self, value: Optional[OutlinedBorder]): - self.__shape = value - - # border_side - @property - def border_side(self) -> ControlStateValue[BorderSide]: - return self.__border_side - - @border_side.setter - def border_side(self, value: ControlStateValue[BorderSide]): - self.__border_side = value - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self.__mouse_cursor - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self.__mouse_cursor = value - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # semantics_label - @property - def semantics_label(self) -> Optional[str]: - return self._get_attr("semanticsLabel") - - @semantics_label.setter - def semantics_label(self, value: Optional[str]): - self._set_attr("semanticsLabel", value) - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_context_menu.py b/sdk/python/packages/flet/src/flet/core/cupertino_context_menu.py deleted file mode 100644 index 199f95898..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_context_menu.py +++ /dev/null @@ -1,85 +0,0 @@ -from typing import Any, List, Optional - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.control import Control -from flet.core.ref import Ref - - -class CupertinoContextMenu(AdaptiveControl): - """ - A full-screen modal route that opens up when the content is long-pressed. - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinocontextmenu - """ - - def __init__( - self, - content: Control, - actions: List[Control], - enable_haptic_feedback: Optional[bool] = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = False, - ): - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.enable_haptic_feedback = enable_haptic_feedback - self.content = content - self.actions = actions - - def _get_control_name(self): - return "cupertinocontextmenu" - - def _get_children(self): - self.__content._set_attr_internal("n", "content") - for action in self.__actions: - action._set_attr_internal("n", "action") - return [self.__content] + self.__actions - - def before_update(self): - super().before_update() - assert ( - len(self.__actions) > 0 - ), "actions must be provided and at least one must be visible" - - # enable_haptic_feedback - @property - def enable_haptic_feedback(self) -> bool: - return self._get_attr("enableHapticFeedback", data_type="bool", def_value=False) - - @enable_haptic_feedback.setter - def enable_haptic_feedback(self, value: Optional[bool]): - self._set_attr("enableHapticFeedback", value) - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # actions - @property - def actions(self) -> List[Control]: - return self.__actions - - @actions.setter - def actions(self, value: List[Control]): - self.__actions = value diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_context_menu_action.py b/sdk/python/packages/flet/src/flet/core/cupertino_context_menu_action.py deleted file mode 100644 index d1a535ae0..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_context_menu_action.py +++ /dev/null @@ -1,111 +0,0 @@ -from typing import Any, Optional - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.control import Control -from flet.core.ref import Ref -from flet.core.types import IconEnums, IconValue, OptionalControlEventCallable - - -class CupertinoContextMenuAction(AdaptiveControl): - """ - An action that can be added to a CupertinoContextMenu. - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinocontextmenuaction - """ - - def __init__( - self, - text: Optional[str] = None, - content: Optional[Control] = None, - is_default_action: Optional[bool] = None, - is_destructive_action: Optional[bool] = None, - trailing_icon: Optional[IconValue] = None, - on_click: OptionalControlEventCallable = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = False, - ): - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.is_default_action = is_default_action - self.is_destructive_action = is_destructive_action - self.content = content - self.trailing_icon = trailing_icon - self.on_click = on_click - self.text = text - - def _get_control_name(self): - return "cupertinocontextmenuaction" - - def _get_children(self): - return [self.__content] if self.__content else [] - - # is_default_action - @property - def is_default_action(self) -> bool: - return self._get_attr("isDefaultAction", data_type="bool", def_value=False) - - @is_default_action.setter - def is_default_action(self, value: Optional[bool]): - self._set_attr("isDefaultAction", value) - - # is_destructive_action - @property - def is_destructive_action(self) -> bool: - return self._get_attr("isDestructiveAction", data_type="bool", def_value=False) - - @is_destructive_action.setter - def is_destructive_action(self, value: Optional[bool]): - self._set_attr("isDestructiveAction", value) - - # trailing_icon - @property - def trailing_icon(self) -> Optional[IconValue]: - return self.__trailing_icon - - @trailing_icon.setter - def trailing_icon(self, value: Optional[IconValue]): - self.__trailing_icon = value - self._set_enum_attr("trailingIcon", value, IconEnums) - - # text - @property - def text(self) -> Optional[str]: - return self._get_attr("text") - - @text.setter - def text(self, value: Optional[str]): - self._set_attr("text", value) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_date_picker.py b/sdk/python/packages/flet/src/flet/core/cupertino_date_picker.py deleted file mode 100644 index 2265fdf51..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_date_picker.py +++ /dev/null @@ -1,253 +0,0 @@ -from datetime import datetime -from enum import Enum -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - DateTimeValue, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class CupertinoDatePickerMode(Enum): - TIME = "time" - DATE = "date" - DATE_AND_TIME = "dateAndTime" - MONTH_YEAR = "monthYear" - - -class CupertinoDatePickerDateOrder(Enum): - DAY_MONTH_YEAR = "dmy" - MONTH_YEAR_DAY = "myd" - YEAR_MONTH_DAY = "ymd" - YEAR_DAY_MONTH = "ydm" - - -class CupertinoDatePicker(ConstrainedControl): - """ - An iOS-styled date picker. - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinodatepicker - """ - - def __init__( - self, - value: DateTimeValue = datetime.now(), - first_date: Optional[DateTimeValue] = None, - last_date: Optional[DateTimeValue] = None, - bgcolor: Optional[ColorValue] = None, - minute_interval: Optional[int] = None, - minimum_year: Optional[int] = None, - maximum_year: Optional[int] = None, - item_extent: OptionalNumber = None, - use_24h_format: Optional[bool] = None, - date_picker_mode: Optional[CupertinoDatePickerMode] = None, - date_order: Optional[CupertinoDatePickerDateOrder] = None, - on_change: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - visible=visible, - disabled=disabled, - data=data, - ) - self.value = value - self.bgcolor = bgcolor - self.minute_interval = minute_interval - self.maximum_year = maximum_year - self.minimum_year = minimum_year - self.item_extent = item_extent - self.first_date = first_date - self.use_24h_format = use_24h_format - self.last_date = last_date - self.date_picker_mode = date_picker_mode - self.date_order = date_order - self.on_change = on_change - - def _get_control_name(self): - return "cupertinodatepicker" - - # value - @property - def value(self) -> datetime: - v = self._get_attr("value") - return datetime.fromisoformat(v) if v else None - - @value.setter - def value(self, value: DateTimeValue): - self._set_attr("value", value.isoformat()) - - # first_date - @property - def first_date(self) -> Optional[datetime]: - v = self._get_attr("firstDate") - return datetime.fromisoformat(v) if v is not None else None - - @first_date.setter - def first_date(self, value: Optional[DateTimeValue]): - self.__first_date = value - self._set_attr("firstDate", value if value is None else value.isoformat()) - - # last_date - @property - def last_date(self) -> Optional[datetime]: - v = self._get_attr("lastDate") - return datetime.fromisoformat(v) if v is not None else None - - @last_date.setter - def last_date(self, value: Optional[DateTimeValue]): - self._set_attr("lastDate", value if value is None else value.isoformat()) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # item_extent - @property - def item_extent(self) -> float: - return self._get_attr("itemExtent", data_type="float", def_value=32.0) - - @item_extent.setter - def item_extent(self, value: OptionalNumber): - assert value is None or value > 0, "item_extent must be greater than 0" - self._set_attr("itemExtent", value) - - # min_year - @property - def min_year(self) -> int: - return self._get_attr("minYear", data_type="int", def_value=1) - - @min_year.setter - def min_year(self, value: Optional[int]): - self._set_attr("minYear", value) - - # max_year - @property - def max_year(self) -> Optional[int]: - return self._get_attr("maxYear", data_type="int") - - @max_year.setter - def max_year(self, value: Optional[int]): - self._set_attr("maxYear", value) - - # minute_interval - @property - def minute_interval(self) -> int: - return self._get_attr("minuteInterval", data_type="int", def_value=1) - - @minute_interval.setter - def minute_interval(self, value: Optional[int]): - assert value is None or ( - value > 0 and 60 % value == 0 - ), "minute_interval must be a positive integer factor of 60" - self._set_attr("minuteInterval", value) - - # use_24h_format - @property - def use_24h_format(self) -> Optional[int]: - return self._get_attr("use24hFormat", data_type="bool", def_value=False) - - @use_24h_format.setter - def use_24h_format(self, value: Optional[int]): - self._set_attr("use24hFormat", value) - - # date_picker_mode - @property - def date_picker_mode(self) -> Optional[CupertinoDatePickerMode]: - return self.__date_picker_mode - - @date_picker_mode.setter - def date_picker_mode(self, value: Optional[CupertinoDatePickerMode]): - self.__date_picker_mode = value - self._set_enum_attr("datePickerMode", value, CupertinoDatePickerMode) - - # date_order - @property - def date_order(self) -> Optional[CupertinoDatePickerDateOrder]: - return self.__date_order - - @date_order.setter - def date_order(self, value: Optional[CupertinoDatePickerDateOrder]): - self.__date_order = value - self._set_enum_attr("dateOrder", value, CupertinoDatePickerDateOrder) - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_dialog_action.py b/sdk/python/packages/flet/src/flet/core/cupertino_dialog_action.py deleted file mode 100644 index 614761c2d..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_dialog_action.py +++ /dev/null @@ -1,158 +0,0 @@ -from typing import Any, Optional - -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.types import OptionalControlEventCallable - - -class CupertinoDialogAction(Control): - """ - A button typically used in a CupertinoAlertDialog. - - Example: - ``` - import flet as ft - - - def main(page: ft.Page): - page.horizontal_alignment = ft.CrossAxisAlignment.CENTER - - def dialog_dismissed(e): - page.add(ft.Text("Dialog dismissed")) - - def handle_action_click(e): - page.add(ft.Text(f"Action clicked: {e.control.text}")) - page.close(cupertino_alert_dialog) - - cupertino_alert_dialog = ft.CupertinoAlertDialog( - title=ft.Text("Cupertino Alert Dialog"), - content=ft.Text("Do you want to delete this file?"), - on_dismiss=dialog_dismissed, - actions=[ - ft.CupertinoDialogAction( - text="Yes", - is_destructive_action=True, - on_click=handle_action_click, - ), - ft.CupertinoDialogAction( - text="No", - is_default_action=True, - on_click=handle_action_click - ), - ], - ) - - page.add( - ft.CupertinoFilledButton( - text="Open CupertinoAlertDialog", - on_click=lambda e: page.open(cupertino_alert_dialog), - ) - ) - - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinodialogaction - """ - - def __init__( - self, - text: Optional[str] = None, - content: Optional[Control] = None, - is_default_action: Optional[bool] = None, - is_destructive_action: Optional[bool] = None, - text_style: Optional[TextStyle] = None, - on_click: OptionalControlEventCallable = None, - # - # Specific - # - ref: Optional[Ref] = None, - opacity: OptionalNumber = None, - visible: Optional[bool] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - opacity=opacity, - visible=visible, - data=data, - ) - - self.text = text - self.content = content - self.on_click = on_click - self.is_default_action = is_default_action - self.is_destructive_action = is_destructive_action - self.text_style = text_style - - def _get_control_name(self): - return "cupertinodialogaction" - - def before_update(self): - super().before_update() - self._set_attr_json("textStyle", self.__text_style) - - def _get_children(self): - if self.__content is None: - return [] - self.__content._set_attr_internal("n", "content") - return [self.__content] - - # text - @property - def text(self) -> Optional[str]: - return self._get_attr("text") - - @text.setter - def text(self, value: Optional[str]): - self._set_attr("text", value) - - # is_default_action - @property - def is_default_action(self) -> bool: - return self._get_attr("isDefaultAction", data_type="bool", def_value=False) - - @is_default_action.setter - def is_default_action(self, value: Optional[bool]): - self._set_attr("isDefaultAction", value) - - # is_destructive_action - @property - def is_destructive_action(self) -> bool: - return self._get_attr("isDestructiveAction", data_type="bool", def_value=False) - - @is_destructive_action.setter - def is_destructive_action(self, value: Optional[bool]): - self._set_attr("isDestructiveAction", value) - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # text_style - @property - def text_style(self) -> Optional[TextStyle]: - return self.__text_style - - @text_style.setter - def text_style(self, value: Optional[TextStyle]): - self.__text_style = value diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_filled_button.py b/sdk/python/packages/flet/src/flet/core/cupertino_filled_button.py deleted file mode 100644 index eb02dda63..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_filled_button.py +++ /dev/null @@ -1,116 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.alignment import Alignment -from flet.core.badge import BadgeValue -from flet.core.control import Control, OptionalNumber -from flet.core.cupertino_button import CupertinoButton -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - BorderRadiusValue, - ColorValue, - IconValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - UrlTarget, -) - - -class CupertinoFilledButton(CupertinoButton): - """ - An iOS-style button filled with default background color. - - Example: - ``` - import flet as ft - - - def main(page: ft.Page): - page.add( - ft.CupertinoFilledButton(text="OK"), - ) - - ft.app(target=main) - ``` - ----- - - Online docs: https://flet.dev/docs/controls/cupertinofilledbutton - """ - - def __init__( - self, - text: Optional[str] = None, - icon: Optional[IconValue] = None, - icon_color: Optional[ColorValue] = None, - content: Optional[Control] = None, - disabled_bgcolor: Optional[ColorValue] = None, - opacity_on_click: OptionalNumber = None, - min_size: OptionalNumber = None, - padding: Optional[PaddingValue] = None, - alignment: Optional[Alignment] = None, - border_radius: Optional[BorderRadiusValue] = None, - url: Optional[str] = None, - autofocus: Optional[bool] = None, - focus_color: Optional[bool] = None, - url_target: Optional[UrlTarget] = None, - on_click: OptionalControlEventCallable = None, - on_long_press: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - CupertinoButton.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - # - # Specific - # - color="onPrimary", - bgcolor="primary", - disabled_bgcolor=disabled_bgcolor, - text=text, - icon=icon, - icon_color=icon_color, - content=content, - url=url, - url_target=url_target, - on_click=on_click, - border_radius=border_radius, - min_size=min_size, - opacity_on_click=opacity_on_click, - padding=padding, - alignment=alignment, - autofocus=autofocus, - focus_color=focus_color, - on_long_press=on_long_press, - on_focus=on_focus, - on_blur=on_blur, - ) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_list_tile.py b/sdk/python/packages/flet/src/flet/core/cupertino_list_tile.py deleted file mode 100644 index 88959fa97..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_list_tile.py +++ /dev/null @@ -1,317 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, - UrlTarget, -) - - -class CupertinoListTile(ConstrainedControl): - """ - An iOS-style list tile. The CupertinoListTile is a Cupertino equivalent of Material ListTile. - - Example: - - ``` - import flet as ft - - - def main(page: ft.Page): - def tile_clicked(e): - print("Tile Clicked!") - - page.add( - ft.CupertinoListTile( - notched=True, - additional_info=ft.Text("Wed Jan 25"), - bgcolor_activated=ft.colors.AMBER_ACCENT, - leading=ft.Icon(name=ft.cupertino_icons.GAME_CONTROLLER), - title=ft.Text("CupertinoListTile not notched"), - subtitle=ft.Text("Subtitle"), - trailing=ft.Icon(name=ft.cupertino_icons.ALARM), - on_click=tile_clicked, - ), - - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinolisttile - """ - - def __init__( - self, - title: Optional[Control] = None, - subtitle: Optional[Control] = None, - leading: Optional[Control] = None, - trailing: Optional[Control] = None, - bgcolor: Optional[ColorValue] = None, - bgcolor_activated: Optional[str] = None, - padding: Optional[PaddingValue] = None, - url: Optional[str] = None, - url_target: Optional[UrlTarget] = None, - toggle_inputs: Optional[bool] = None, - additional_info: Optional[Control] = None, - leading_size: OptionalNumber = None, - leading_to_title: OptionalNumber = None, - notched: Optional[bool] = None, - on_click: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.leading = leading - self.title = title - self.subtitle = subtitle - self.trailing = trailing - self.bgcolor = bgcolor - self.bgcolor_activated = bgcolor_activated - self.url = url - self.url_target = url_target - self.toggle_inputs = toggle_inputs - self.additional_info = additional_info - self.leading_size = leading_size - self.leading_to_title = leading_to_title - self.padding = padding - self.notched = notched - self.on_click = on_click - - def _get_control_name(self): - return "cupertinolisttile" - - def before_update(self): - super().before_update() - self._set_attr_json("padding", self.__padding) - - def _get_children(self): - children = [] - if self.__leading: - self.__leading._set_attr_internal("n", "leading") - children.append(self.__leading) - if self.__title: - self.__title._set_attr_internal("n", "title") - children.append(self.__title) - if self.__subtitle: - self.__subtitle._set_attr_internal("n", "subtitle") - children.append(self.__subtitle) - if self.__trailing: - self.__trailing._set_attr_internal("n", "trailing") - children.append(self.__trailing) - if self.__additional_info: - self.__additional_info._set_attr_internal("n", "additionalInfo") - children.append(self.__additional_info) - return children - - # leading - @property - def leading(self) -> Optional[Control]: - return self.__leading - - @leading.setter - def leading(self, value: Optional[Control]): - self.__leading = value - - # leading_size - @property - def leading_size(self) -> float: - return self._get_attr("leadingSize", data_type="float", def_value=30.0) - - @leading_size.setter - def leading_size(self, value: OptionalNumber): - self._set_attr("leadingSize", value) - - # leading_to_title - @property - def leading_to_title(self) -> float: - return self._get_attr("leadingToTitle", data_type="float", def_value=12.0) - - @leading_to_title.setter - def leading_to_title(self, value: OptionalNumber): - self._set_attr("leadingToTitle", value) - - # title - @property - def title(self) -> Optional[Control]: - return self.__title - - @title.setter - def title(self, value: Optional[Control]): - self.__title = value - - # subtitle - @property - def subtitle(self) -> Optional[Control]: - return self.__subtitle - - @subtitle.setter - def subtitle(self, value: Optional[Control]): - self.__subtitle = value - - # trailing - @property - def trailing(self) -> Optional[Control]: - return self.__trailing - - @trailing.setter - def trailing(self, value: Optional[Control]): - self.__trailing = value - - # additional_info - @property - def additional_info(self) -> Optional[Control]: - return self.__additional_info - - @additional_info.setter - def additional_info(self, value: Optional[Control]): - self.__additional_info = value - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # notched - @property - def notched(self) -> bool: - return self._get_attr("notched", data_type="bool", def_value=False) - - @notched.setter - def notched(self, value: Optional[bool]): - self._set_attr("notched", value) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # bgcolor_activated - @property - def bgcolor_activated(self) -> Optional[str]: - return self._get_attr("bgcolorActivated") - - @bgcolor_activated.setter - def bgcolor_activated(self, value: Optional[str]): - self._set_attr("bgcolorActivated", value) - - # url - @property - def url(self) -> Optional[str]: - return self._get_attr("url") - - @url.setter - def url(self, value: Optional[str]): - self._set_attr("url", value) - - # url_target - @property - def url_target(self) -> Optional[UrlTarget]: - return self.__url_target - - @url_target.setter - def url_target(self, value: Optional[UrlTarget]): - self.__url_target = value - self._set_enum_attr("urlTarget", value, UrlTarget) - - # toggle_inputs - @property - def toggle_inputs(self) -> bool: - return self._get_attr("toggleInputs", data_type="bool", def_value=False) - - @toggle_inputs.setter - def toggle_inputs(self, value: Optional[bool]): - self._set_attr("toggleInputs", value) - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) - self._set_attr("onclick", True if handler is not None else None) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_navigation_bar.py b/sdk/python/packages/flet/src/flet/core/cupertino_navigation_bar.py deleted file mode 100644 index e57a49565..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_navigation_bar.py +++ /dev/null @@ -1,219 +0,0 @@ -from typing import Any, List, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.border import Border -from flet.core.constrained_control import ConstrainedControl -from flet.core.navigation_bar import NavigationBarDestination -from flet.core.ref import Ref -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - OptionalNumber, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class CupertinoNavigationBar(ConstrainedControl): - """ - An iOS-styled bottom navigation tab bar. - - Navigation bars offer a persistent and convenient way to switch between primary destinations in an app. - - Example: - - ``` - import flet as ft - - def main(page: ft.Page): - page.title = "CupertinoNavigationBar Example" - page.navigation_bar = ft.CupertinoNavigationBar( - bgcolor=ft.colors.AMBER_100, - inactive_color=ft.colors.GREY, - active_color=ft.colors.BLACK, - on_change=lambda e: print("Selected tab:", e.control.selected_index), - destinations=[ - ft.NavigationBarDestination(icon=ft.icons.EXPLORE, label="Explore"), - ft.NavigationBarDestination(icon=ft.icons.COMMUTE, label="Commute"), - ft.NavigationBarDestination( - icon=ft.icons.BOOKMARK_BORDER, - selected_icon=ft.icons.BOOKMARK, - label="Explore", - ), - ] - ) - page.add(ft.SafeArea(ft.Text("Body!"))) - - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinonavigationbar - """ - - def __init__( - self, - destinations: Optional[List[NavigationBarDestination]] = None, - selected_index: Optional[int] = None, - bgcolor: Optional[ColorValue] = None, - active_color: Optional[ColorValue] = None, - inactive_color: Optional[ColorValue] = None, - border: Optional[Border] = None, - icon_size: OptionalNumber = None, - on_change: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - ) - - self.destinations = destinations - self.selected_index = selected_index - self.bgcolor = bgcolor - self.active_color = active_color - self.inactive_color = inactive_color - self.border = border - self.icon_size = icon_size - self.on_change = on_change - - def _get_control_name(self): - return "cupertinonavigationbar" - - def before_update(self) -> None: - super().before_update() - self._set_attr_json("border", self.__border) - - def _get_children(self): - return self.__destinations - - # destinations - @property - def destinations(self) -> Optional[List[NavigationBarDestination]]: - return self.__destinations - - @destinations.setter - def destinations(self, value: Optional[List[NavigationBarDestination]]): - self.__destinations = value if value else [] - - # border - @property - def border(self) -> Optional[Border]: - return self.__border - - @border.setter - def border(self, value: Optional[Border]): - self.__border = value - - # selected_index - @property - def selected_index(self) -> int: - return self._get_attr("selectedIndex", data_type="int", def_value=0) - - @selected_index.setter - def selected_index(self, value: Optional[int]): - self._set_attr("selectedIndex", value) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # active_color - @property - def active_color(self) -> Optional[ColorValue]: - return self.__active_color - - @active_color.setter - def active_color(self, value: Optional[ColorValue]): - self.__active_color = value - self._set_enum_attr("activeColor", value, ColorEnums) - - # inactive_color - @property - def inactive_color(self) -> Optional[ColorValue]: - return self.__inactive_color - - @inactive_color.setter - def inactive_color(self, value: Optional[ColorValue]): - self.__inactive_color = value - self._set_enum_attr("inactiveColor", value, ColorEnums) - - # icon_size - @property - def icon_size(self) -> OptionalNumber: - return self._get_attr("iconSize") - - @icon_size.setter - def icon_size(self, value: OptionalNumber): - self._set_attr("iconSize", value) - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_picker.py b/sdk/python/packages/flet/src/flet/core/cupertino_picker.py deleted file mode 100644 index b2326812c..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_picker.py +++ /dev/null @@ -1,256 +0,0 @@ -from typing import Any, List, Optional, Sequence, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class CupertinoPicker(ConstrainedControl): - """ - An iOS-styled picker. - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinopicker - """ - - def __init__( - self, - controls: Sequence[Control], - item_extent: OptionalNumber = None, - selected_index: Optional[int] = None, - bgcolor: Optional[ColorValue] = None, - use_magnifier: Optional[bool] = None, - looping: Optional[bool] = None, - magnification: OptionalNumber = None, - squeeze: OptionalNumber = None, - diameter_ratio: OptionalNumber = None, - off_axis_fraction: OptionalNumber = None, - selection_overlay: Optional[Control] = None, - default_selection_overlay_bgcolor: Optional[ColorValue] = None, - on_change: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.squeeze = squeeze - self.bgcolor = bgcolor - self.on_change = on_change - self.magnification = magnification - self.diameter_ratio = diameter_ratio - self.off_axis_fraction = off_axis_fraction - self.use_magnifier = use_magnifier - self.item_extent = item_extent - self.controls = controls - self.looping = looping - self.selected_index = selected_index - self.selection_overlay = selection_overlay - self.default_selection_overlay_bgcolor = default_selection_overlay_bgcolor - - def _get_control_name(self): - return "cupertinopicker" - - def _get_children(self): - children = self.__controls - if self.__selection_overlay: - self.__selection_overlay._set_attr_internal("n", "selection_overlay") - children.append(self.__selection_overlay) - return children - - # squeeze - @property - def squeeze(self) -> float: - return self._get_attr("squeeze", data_type="float", def_value=1.45) - - @squeeze.setter - def squeeze(self, value: OptionalNumber): - if value is not None and value <= 0: - raise ValueError("CupertinoPicker.squeeze must be greater than 0") - self._set_attr("squeeze", value) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # use_magnifier - @property - def use_magnifier(self) -> bool: - return self._get_attr("useMagnifier", data_type="bool", def_value=False) - - @use_magnifier.setter - def use_magnifier(self, value: Optional[bool]): - self._set_attr("useMagnifier", value) - - # magnification - @property - def magnification(self) -> float: - return self._get_attr("magnification", data_type="float", def_value=1.0) - - @magnification.setter - def magnification(self, value: OptionalNumber): - if value is not None and value <= 0: - raise ValueError("CupertinoPicker.magnification must be greater than 0") - self._set_attr("magnification", value) - - # item_extent - @property - def item_extent(self) -> OptionalNumber: - return self._get_attr("itemExtent", data_type="float") - - @item_extent.setter - def item_extent(self, value: OptionalNumber): - assert ( - value is None or value > 0 - ), "item_extent cannot be negative or equal to 0" - self._set_attr("itemExtent", value) - - # looping - @property - def looping(self) -> bool: - return self._get_attr("looping", data_type="bool", def_value=False) - - @looping.setter - def looping(self, value: Optional[bool]): - self._set_attr("looping", value) - - # selected_index - @property - def selected_index(self) -> int: - return self._get_attr("selectedIndex", data_type="int", def_value=0) - - @selected_index.setter - def selected_index(self, value: Optional[int]): - self._set_attr("selectedIndex", value) - - # diameter_ratio - @property - def diameter_ratio(self) -> float: - return self._get_attr("diameterRatio", data_type="float", def_value=1.07) - - @diameter_ratio.setter - def diameter_ratio(self, value: OptionalNumber): - self._set_attr("diameterRatio", value) - - # off_axis_fraction - @property - def off_axis_fraction(self) -> float: - return self._get_attr("offAxisFraction", data_type="float", def_value=0.0) - - @off_axis_fraction.setter - def off_axis_fraction(self, value: OptionalNumber): - self._set_attr("offAxisFraction", value) - - # controls - @property - def controls(self) -> List[Control]: - return self.__controls - - @controls.setter - def controls(self, value: Sequence[Control]): - self.__controls = list(value) - - # selection_overlay - @property - def selection_overlay(self) -> Optional[Control]: - return self.__selection_overlay - - @selection_overlay.setter - def selection_overlay(self, value: Optional[Control]): - self.__selection_overlay = value - - # default_selection_overlay_bgcolor - @property - def default_selection_overlay_bgcolor(self) -> Optional[ColorValue]: - return self.__default_selection_overlay_bgcolor - - @default_selection_overlay_bgcolor.setter - def default_selection_overlay_bgcolor(self, value: Optional[ColorValue]): - self.__default_selection_overlay_bgcolor = value - self._set_enum_attr("defaultSelectionOverlayBgcolor", value, ColorEnums) - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_radio.py b/sdk/python/packages/flet/src/flet/core/cupertino_radio.py deleted file mode 100644 index ef861c8e9..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_radio.py +++ /dev/null @@ -1,252 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - LabelPosition, - MouseCursor, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal - - -class CupertinoRadio(ConstrainedControl): - """ - Radio buttons let people select a single option from two or more choices. - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinoradio - """ - - def __init__( - self, - label: Optional[str] = None, - value: Optional[str] = None, - label_position: Optional[LabelPosition] = None, - fill_color: Optional[ColorValue] = None, - active_color: Optional[ColorValue] = None, - inactive_color: Optional[ColorValue] = None, - autofocus: Optional[bool] = None, - use_checkmark_style: Optional[bool] = None, - toggleable: Optional[bool] = None, - focus_color: Optional[ColorValue] = None, - mouse_cursor: Optional[MouseCursor] = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - self.value = value - self.label = label - self.label_position = label_position - self.autofocus = autofocus - self.use_checkmark_style = use_checkmark_style - self.fill_color = fill_color - self.active_color = active_color - self.inactive_color = inactive_color - self.on_focus = on_focus - self.on_blur = on_blur - self.focus_color = focus_color - self.toggleable = toggleable - self.mouse_cursor = mouse_cursor - - def _get_control_name(self): - return "cupertinoradio" - - # value - @property - def value(self) -> Optional[str]: - return self._get_attr("value", def_value="") - - @value.setter - def value(self, value: Optional[str]): - self._set_attr("value", value) - - # label - @property - def label(self) -> Optional[str]: - return self._get_attr("label") - - @label.setter - def label(self, value: Optional[str]): - self._set_attr("label", value) - - # label_position - @property - def label_position(self) -> Optional[LabelPosition]: - return self.__label_position - - @label_position.setter - def label_position(self, value: Optional[LabelPosition]): - self.__label_position = value - self._set_enum_attr("labelPosition", value, LabelPosition) - - # fill_color - @property - def fill_color(self) -> Optional[ColorValue]: - return self.__fill_color - - @fill_color.setter - def fill_color(self, value: Optional[ColorValue]): - self.__fill_color = value - self._set_enum_attr("fillColor", value, ColorEnums) - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self.__mouse_cursor - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self.__mouse_cursor = value - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # focus_color - @property - def focus_color(self) -> Optional[ColorValue]: - return self.__focus_color - - @focus_color.setter - def focus_color(self, value: Optional[ColorValue]): - self.__focus_color = value - self._set_enum_attr("focusColor", value, ColorEnums) - - # toggleable - @property - def toggleable(self) -> bool: - return self._get_attr("toggleable", data_type="bool", def_value=False) - - @toggleable.setter - def toggleable(self, value: Optional[bool]): - self._set_attr("toggleable", value) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # use_checkmark_style - @property - def use_checkmark_style(self) -> bool: - return self._get_attr("useCheckmarkStyle", data_type="bool", def_value=False) - - @use_checkmark_style.setter - def use_checkmark_style(self, value: Optional[bool]): - self._set_attr("useCheckmarkStyle", value) - - # active_color - @property - def active_color(self) -> Optional[ColorValue]: - return self.__active_color - - @active_color.setter - def active_color(self, value: Optional[ColorValue]): - self.__active_color = value - self._set_enum_attr("activeColor", value, ColorEnums) - - # inactive_color - @property - def inactive_color(self) -> Optional[ColorValue]: - return self.__inactive_color - - @inactive_color.setter - def inactive_color(self, value: Optional[ColorValue]): - self.__inactive_color = value - self._set_enum_attr("inactiveColor", value, ColorEnums) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_segmented_button.py b/sdk/python/packages/flet/src/flet/core/cupertino_segmented_button.py deleted file mode 100644 index eee03310b..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_segmented_button.py +++ /dev/null @@ -1,228 +0,0 @@ -from typing import Any, List, Optional, Sequence, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class CupertinoSegmentedButton(ConstrainedControl): - """ - An iOS-style segmented button. - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinosegmentedbutton - """ - - def __init__( - self, - controls: Sequence[Control], - selected_index: Optional[int] = None, - selected_color: Optional[ColorValue] = None, - unselected_color: Optional[ColorValue] = None, - border_color: Optional[ColorValue] = None, - padding: Optional[PaddingValue] = None, - click_color: Optional[ColorValue] = None, - disabled_color: Optional[ColorValue] = None, - disabled_text_color: Optional[ColorValue] = None, - on_change: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - self.controls = controls - self.padding = padding - self.border_color = border_color - self.selected_index = selected_index - self.selected_color = selected_color - self.unselected_color = unselected_color - self.on_change = on_change - self.click_color = click_color - self.disabled_color = disabled_color - self.disabled_text_color = disabled_text_color - - def _get_control_name(self): - return "cupertinosegmentedbutton" - - def _get_children(self): - return self.__controls - - def before_update(self): - super().before_update() - assert ( - len(self.__controls) >= 2 - ), "CupertinoSegmentedButton must have at minimum two visible controls" - - def _before_build_command(self): - super()._before_build_command() - self._set_attr_json("padding", self.__padding) - - # controls - @property - def controls(self) -> List[Control]: - return self.__controls - - @controls.setter - def controls(self, value: Sequence[Control]): - self.__controls = list(value) - - # border_color - @property - def border_color(self) -> Optional[ColorValue]: - return self.__border_color - - @border_color.setter - def border_color(self, value: Optional[ColorValue]): - self.__border_color = value - self._set_enum_attr("borderColor", value, ColorEnums) - - # disabled_color - @property - def disabled_color(self) -> Optional[ColorValue]: - return self.__disabled_color - - @disabled_color.setter - def disabled_color(self, value: Optional[ColorValue]): - self.__disabled_color = value - self._set_enum_attr("disabledColor", value, ColorEnums) - - # disabled_text_color - @property - def disabled_text_color(self) -> Optional[ColorValue]: - return self.__disabled_text_color - - @disabled_text_color.setter - def disabled_text_color(self, value: Optional[ColorValue]): - self.__disabled_text_color = value - self._set_enum_attr("disabledTextColor", value, ColorEnums) - - # selected_index - @property - def selected_index(self) -> int: - return self._get_attr("selectedIndex", data_type="int", def_value=0) - - @selected_index.setter - def selected_index(self, value: Optional[int]): - if value is not None and not (0 <= value < len(self.controls)): - raise IndexError("selected_index out of range") - self._set_attr("selectedIndex", value) - - # selected_color - @property - def selected_color(self) -> Optional[ColorValue]: - return self.__selected_color - - @selected_color.setter - def selected_color(self, value: Optional[ColorValue]): - self.__selected_color = value - self._set_enum_attr("selectedColor", value, ColorEnums) - - # unselected_color - @property - def unselected_color(self) -> Optional[ColorValue]: - return self.__unselected_color - - @unselected_color.setter - def unselected_color(self, value: Optional[ColorValue]): - self.__unselected_color = value - self._set_enum_attr("unselectedColor", value, ColorEnums) - - # click_color - @property - def click_color(self) -> Optional[ColorValue]: - return self.__click_color - - @click_color.setter - def click_color(self, value: Optional[ColorValue]): - self.__click_color = value - self._set_enum_attr("clickColor", value, ColorEnums) - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_slider.py b/sdk/python/packages/flet/src/flet/core/cupertino_slider.py deleted file mode 100644 index 41807d51e..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_slider.py +++ /dev/null @@ -1,245 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class CupertinoSlider(ConstrainedControl): - """ - An iOS-type slider. - - It provides a visual indication of adjustable content, as well as the current setting in the total range of content. - - Use a slider when you want people to set defined values (such as volume or brightness), or when people would benefit from instant feedback on the effect of setting changes. - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinoslider - """ - - def __init__( - self, - value: OptionalNumber = None, - min: OptionalNumber = None, - max: OptionalNumber = None, - divisions: Optional[int] = None, - active_color: Optional[ColorValue] = None, - thumb_color: Optional[ColorValue] = None, - on_change: OptionalControlEventCallable = None, - on_change_start: OptionalControlEventCallable = None, - on_change_end: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - self.value = value - self.min = min - self.max = max - self.divisions = divisions - self.round = round - self.active_color = active_color - self.thumb_color = thumb_color - self.on_change = on_change - self.on_change_start = on_change_start - self.on_change_end = on_change_end - self.on_focus = on_focus - self.on_blur = on_blur - - def _get_control_name(self): - return "cupertinoslider" - - def before_update(self): - super().before_update() - assert ( - self.min is None or self.max is None or self.min <= self.max - ), "min must be less than or equal to max" - assert ( - self.min is None or self.value is None or (self.value >= self.min) - ), "value must be greater than or equal to min" - assert ( - self.max is None or self.value is None or (self.value <= self.max) - ), "value must be less than or equal to max" - - # value - @property - def value(self) -> float: - return self._get_attr("value", data_type="float", def_value=self.min or 0.0) - - @value.setter - def value(self, value: OptionalNumber): - self._set_attr("value", value) - - # min - @property - def min(self) -> float: - return self._get_attr("min", data_type="float", def_value=0.0) - - @min.setter - def min(self, value: OptionalNumber): - self._set_attr("min", value) - - # max - @property - def max(self) -> float: - return self._get_attr("max", data_type="float", def_value=1.0) - - @max.setter - def max(self, value: OptionalNumber): - self._set_attr("max", value) - - # divisions - @property - def divisions(self) -> Optional[int]: - return self._get_attr("divisions", data_type="int") - - @divisions.setter - def divisions(self, value: Optional[int]): - self._set_attr("divisions", value) - - # round - @property - def round(self) -> int: - return self._get_attr("round", data_type="int", def_value=0) - - @round.setter - def round(self, value: Optional[int]): - self._set_attr("round", value) - - # active_color - @property - def active_color(self) -> Optional[ColorValue]: - return self.__active_color - - @active_color.setter - def active_color(self, value: Optional[ColorValue]): - self.__active_color = value - self._set_enum_attr("activeColor", value, ColorEnums) - - # thumb_color - @property - def thumb_color(self) -> Optional[ColorValue]: - return self.__thumb_color - - @thumb_color.setter - def thumb_color(self, value: Optional[ColorValue]): - self.__thumb_color = value - self._set_enum_attr("thumbColor", value, ColorEnums) - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - - # on_change_start - @property - def on_change_start(self) -> OptionalControlEventCallable: - return self._get_event_handler("change_start") - - @on_change_start.setter - def on_change_start(self, handler: OptionalControlEventCallable): - self._add_event_handler("change_start", handler) - - # on_change_end - @property - def on_change_end(self) -> OptionalControlEventCallable: - return self._get_event_handler("change_end") - - @on_change_end.setter - def on_change_end(self, handler: OptionalControlEventCallable): - self._add_event_handler("change_end", handler) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_sliding_segmented_button.py b/sdk/python/packages/flet/src/flet/core/cupertino_sliding_segmented_button.py deleted file mode 100644 index c37d27fbf..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_sliding_segmented_button.py +++ /dev/null @@ -1,189 +0,0 @@ -from typing import Any, Optional, Sequence, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class CupertinoSlidingSegmentedButton(ConstrainedControl): - """ - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinoslidingsegmentedbutton - """ - - def __init__( - self, - controls: Sequence[Control], - selected_index: Optional[int] = None, - bgcolor: Optional[ColorValue] = None, - thumb_color: Optional[ColorValue] = None, - padding: Optional[PaddingValue] = None, - proportional_width: Optional[bool] = None, - on_change: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - self.controls = controls - self.padding = padding - self.selected_index = selected_index - self.bgcolor = bgcolor - self.thumb_color = thumb_color - self.on_change = on_change - self.proportional_width = proportional_width - - def _get_control_name(self): - return "cupertinoslidingsegmentedbutton" - - def _get_children(self): - return self.__controls - - def before_update(self): - super().before_update() - assert ( - len(self.__controls) >= 2 - ), "CupertinoSlidingSegmentedButton must have at minimum two visible controls" - self._set_attr_json("padding", self.__padding) - - # controls - @property - def controls(self): - return self.__controls - - @controls.setter - def controls(self, value: Sequence[Control]): - self.__controls = list(value) - - # selected_index - @property - def selected_index(self) -> int: - return self._get_attr("selectedIndex", data_type="int", def_value=0) - - @selected_index.setter - def selected_index(self, value: Optional[int]): - assert ( - value is None or 0 <= value <= len(self.controls) - 1 - ), "selected_index out of range" - self._set_attr("selectedIndex", value) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # proportional_width - - @property - def proportional_width(self) -> Optional[bool]: - return self._get_attr("proportionalWidth", data_type="bool", def_value=False) - - @proportional_width.setter - def proportional_width(self, value: Optional[bool]): - self._set_attr("proportionalWidth", value) - - # thumb_color - @property - def thumb_color(self) -> Optional[ColorValue]: - return self.__thumb_color - - @thumb_color.setter - def thumb_color(self, value: Optional[ColorValue]): - self.__thumb_color = value - self._set_enum_attr("thumbColor", value, ColorEnums) - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_switch.py b/sdk/python/packages/flet/src/flet/core/cupertino_switch.py deleted file mode 100644 index 482cd6384..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_switch.py +++ /dev/null @@ -1,395 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - ControlStateValue, - IconValue, - LabelPosition, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) -from flet.utils.deprecated import deprecated_property - - -class CupertinoSwitch(ConstrainedControl): - """ - An iOS-style switch. Used to toggle the on/off state of a single setting. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.add( - ft.CupertinoSwitch(label="Cupertino Switch", value=True), - ft.Switch(label="Material Checkbox", value=True), - ft.Container(height=20), - ft.Text( - "Adaptive Switch shows as CupertinoSwitch on macOS and iOS and as Switch on other platforms:" - ), - ft.Switch(adaptive=True, label="Adaptive Switch", value=True), - ) - - ft.app(target=main) - ``` - ----- - - Online docs: https://flet.dev/docs/controls/cupertinoswitch - """ - - def __init__( - self, - label: Optional[str] = None, - value: Optional[bool] = None, - label_position: Optional[LabelPosition] = None, - active_color: Optional[ColorValue] = None, - thumb_color: Optional[ColorValue] = None, - track_color: Optional[ColorValue] = None, - focus_color: Optional[ColorValue] = None, - autofocus: Optional[bool] = None, - on_label_color: Optional[ColorValue] = None, - off_label_color: Optional[ColorValue] = None, - active_thumb_image: Optional[str] = None, - inactive_thumb_image: Optional[str] = None, - active_track_color: Optional[ColorValue] = None, - inactive_thumb_color: Optional[ColorValue] = None, - inactive_track_color: Optional[ColorValue] = None, - track_outline_color: ControlStateValue[ColorValue] = None, - track_outline_width: ControlStateValue[OptionalNumber] = None, - thumb_icon: ControlStateValue[IconValue] = None, - on_change: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - on_image_error: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - self.value = value - self.label = label - self.label_position = label_position - self.autofocus = autofocus - self.active_color = active_color - self.focus_color = focus_color - self.thumb_color = thumb_color - self.track_color = track_color - self.on_change = on_change - self.on_focus = on_focus - self.on_blur = on_blur - self.on_label_color = on_label_color - self.off_label_color = off_label_color - self.active_thumb_image = active_thumb_image - self.active_track_color = active_track_color - self.inactive_thumb_color = inactive_thumb_color - self.inactive_track_color = inactive_track_color - self.track_outline_color = track_outline_color - self.track_outline_width = track_outline_width - self.thumb_icon = thumb_icon - self.inactive_thumb_image = inactive_thumb_image - self.on_image_error = on_image_error - - def _get_control_name(self): - return "cupertinoswitch" - - def before_update(self): - super().before_update() - self._set_attr_json("thumbColor", self.__thumb_color) - self._set_attr_json("trackColor", self.__track_color) - self._set_attr_json( - "trackOutlineColor", self.__track_outline_color, wrap_attr_dict=True - ) - self._set_attr_json( - "trackOutlineWidth", self.__track_outline_width, wrap_attr_dict=True - ) - self._set_attr_json("thumbIcon", self.__thumb_icon, wrap_attr_dict=True) - - # value - @property - def value(self) -> bool: - return self._get_attr("value", data_type="bool", def_value=False) - - @value.setter - def value(self, value: Optional[bool]): - self._set_attr("value", value) - - # label - @property - def label(self) -> Optional[str]: - return self._get_attr("label") - - @label.setter - def label(self, value: Optional[str]): - self._set_attr("label", value) - - # active_thumb_image - @property - def active_thumb_image(self) -> Optional[str]: - return self._get_attr("activeThumbImage") - - @active_thumb_image.setter - def active_thumb_image(self, value: Optional[str]): - self._set_attr("activeThumbImage", value) - - # inactive_thumb_image - @property - def inactive_thumb_image(self) -> Optional[str]: - return self._get_attr("inactiveThumbImage") - - @inactive_thumb_image.setter - def inactive_thumb_image(self, value: Optional[str]): - self._set_attr("inactiveThumbImage", value) - - # active_track_color - @property - def active_track_color(self) -> Optional[ColorValue]: - return self.__active_track_color - - @active_track_color.setter - def active_track_color(self, value: Optional[ColorValue]): - self.__active_track_color = value - self._set_enum_attr("activeTrackColor", value, ColorEnums) - - # inactive_track_color - @property - def inactive_track_color(self) -> Optional[ColorValue]: - return self.__inactive_track_color - - @inactive_track_color.setter - def inactive_track_color(self, value: Optional[ColorValue]): - self.__inactive_track_color = value - self._set_enum_attr("inactiveTrackColor", value, ColorEnums) - - # track_outline_color - @property - def track_outline_color(self) -> ControlStateValue[ColorValue]: - return self.__track_outline_color - - @track_outline_color.setter - def track_outline_color(self, value: ControlStateValue[ColorValue]): - self.__track_outline_color = value - - # track_outline_width - @property - def track_outline_width(self) -> ControlStateValue[OptionalNumber]: - return self.__track_outline_width - - @track_outline_width.setter - def track_outline_width(self, value: ControlStateValue[OptionalNumber]): - self.__track_outline_width = value - - # thumb_icon - @property - def thumb_icon(self) -> ControlStateValue[IconValue]: - return self.__thumb_icon - - @thumb_icon.setter - def thumb_icon(self, value: ControlStateValue[IconValue]): - self.__thumb_icon = value - - # label_position - @property - def label_position(self) -> Optional[LabelPosition]: - return self.__label_position - - @label_position.setter - def label_position(self, value: Optional[LabelPosition]): - self.__label_position = value - self._set_enum_attr("labelPosition", value, LabelPosition) - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # active_color - @property - def active_color(self) -> Optional[ColorValue]: - deprecated_property( - name="active_color", - reason="Use active_track_color instead.", - version="0.26.0", - delete_version="0.29.0", - ) - return self.__active_color - - @active_color.setter - def active_color(self, value: Optional[ColorValue]): - self.__active_color = value - self._set_enum_attr("activeColor", value, ColorEnums) - if value is not None: - deprecated_property( - name="active_color", - reason="Use active_track_color instead.", - version="0.26.0", - delete_version="0.29.0", - ) - - # focus_color - @property - def focus_color(self) -> Optional[ColorValue]: - return self.__focus_color - - @focus_color.setter - def focus_color(self, value: Optional[ColorValue]): - self.__focus_color = value - self._set_enum_attr("focusColor", value, ColorEnums) - - # thumb_color - @property - def thumb_color(self) -> Optional[ColorValue]: - return self.__thumb_color - - @thumb_color.setter - def thumb_color(self, value: Optional[ColorValue]): - self.__thumb_color = value - - # track_color - @property - def track_color(self) -> Optional[ColorValue]: - deprecated_property( - name="track_color", - reason="Use inactive_track_color instead.", - version="0.26.0", - delete_version="0.29.0", - ) - return self.__track_color - - @track_color.setter - def track_color(self, value: Optional[ColorValue]): - self.__track_color = value - if value is not None: - deprecated_property( - name="track_color", - reason="Use inactive_track_color instead.", - version="0.26.0", - delete_version="0.29.0", - ) - - # on_label_color - @property - def on_label_color(self) -> Optional[ColorValue]: - return self.__on_label_color - - @on_label_color.setter - def on_label_color(self, value: Optional[ColorValue]): - self.__on_label_color = value - self._set_enum_attr("onLabelColor", value, ColorEnums) - - # off_label_color - @property - def off_label_color(self) -> Optional[ColorValue]: - return self.__off_label_color - - @off_label_color.setter - def off_label_color(self, value: Optional[ColorValue]): - self.__off_label_color = value - self._set_enum_attr("offLabelColor", value, ColorEnums) - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) - - # on_image_error - @property - def on_image_error(self) -> OptionalControlEventCallable: - return self._get_event_handler("image_error") - - @on_image_error.setter - def on_image_error(self, handler: OptionalControlEventCallable): - self._add_event_handler("image_error", handler) diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_textfield.py b/sdk/python/packages/flet/src/flet/core/cupertino_textfield.py deleted file mode 100644 index 59cb53a90..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_textfield.py +++ /dev/null @@ -1,376 +0,0 @@ -from enum import Enum -from typing import Any, List, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.autofill_group import AutofillHint -from flet.core.badge import BadgeValue -from flet.core.border import Border -from flet.core.box import BoxShadow, DecorationImage -from flet.core.control import Control, OptionalNumber -from flet.core.gradients import Gradient -from flet.core.ref import Ref -from flet.core.text_style import StrutStyle, TextStyle -from flet.core.textfield import InputFilter, KeyboardType, TextCapitalization, TextField -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - BlendMode, - BorderRadiusValue, - Brightness, - ClipBehavior, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, - TextAlign, -) - - -class VisibilityMode(Enum): - NEVER = "never" - EDITING = "editing" - NOT_EDITING = "notEditing" - ALWAYS = "always" - - -class CupertinoTextField(TextField): - """ - An iOS-style text field. - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinotextfield - """ - - def __init__( - self, - placeholder_text: Optional[str] = None, - value: Optional[str] = None, - placeholder_style: Optional[TextStyle] = None, - gradient: Optional[Gradient] = None, - blend_mode: Optional[BlendMode] = None, - shadow: Union[None, BoxShadow, List[BoxShadow]] = None, - prefix_visibility_mode: Optional[VisibilityMode] = None, - suffix_visibility_mode: Optional[VisibilityMode] = None, - clear_button_visibility_mode: Optional[VisibilityMode] = None, - clear_button_semantics_label: Optional[str] = None, - image: Optional[DecorationImage] = None, - padding: Optional[PaddingValue] = None, - # - # TextField - # - keyboard_type: Optional[KeyboardType] = None, - rtl: Optional[bool] = None, - multiline: Optional[bool] = None, - min_lines: Optional[int] = None, - max_lines: Optional[int] = None, - max_length: Optional[int] = None, - password: Optional[bool] = None, - can_reveal_password: Optional[bool] = None, - read_only: Optional[bool] = None, - shift_enter: Optional[bool] = None, - text_align: Optional[TextAlign] = None, - autofocus: Optional[bool] = None, - capitalization: Optional[TextCapitalization] = None, - autocorrect: Optional[bool] = None, - enable_suggestions: Optional[bool] = None, - smart_dashes_type: Optional[bool] = None, - smart_quotes_type: Optional[bool] = None, - cursor_color: Optional[ColorValue] = None, - cursor_width: OptionalNumber = None, - cursor_height: OptionalNumber = None, - cursor_radius: OptionalNumber = None, - show_cursor: Optional[bool] = None, - selection_color: Optional[ColorValue] = None, - input_filter: Optional[InputFilter] = None, - autofill_hints: Union[None, AutofillHint, List[AutofillHint]] = None, - enable_scribble: Optional[bool] = None, - scroll_padding: Optional[PaddingValue] = None, - obscuring_character: Optional[str] = None, - enable_interactive_selection: Optional[bool] = None, - enable_ime_personalized_learning: Optional[bool] = None, - clip_behavior: Optional[ClipBehavior] = None, - keyboard_brightness: Optional[Brightness] = None, - strut_style: Optional[StrutStyle] = None, - animate_cursor_opacity: Optional[bool] = None, - on_click: OptionalControlEventCallable = None, - on_change: OptionalControlEventCallable = None, - on_submit: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - on_tap_outside: OptionalControlEventCallable = None, - # - # FormField - # - text_size: OptionalNumber = None, - text_style: Optional[TextStyle] = None, - border: Optional[Border] = None, - color: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, - border_radius: Optional[BorderRadiusValue] = None, - focused_color: Optional[ColorValue] = None, - focused_bgcolor: Optional[ColorValue] = None, - focused_border_width: OptionalNumber = None, - focused_border_color: Optional[ColorValue] = None, - content_padding: Optional[PaddingValue] = None, - dense: Optional[bool] = None, - filled: Optional[bool] = None, - prefix: Optional[Control] = None, - suffix: Optional[Control] = None, - fit_parent_size: Optional[bool] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - TextField.__init__( - self, - ref=ref, - key=key, - badge=badge, - width=width, - height=height, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - visible=visible, - disabled=disabled, - data=data, - # - # FormField - # - text_size=text_size, - text_style=text_style, - color=color, - bgcolor=bgcolor, - border_radius=border_radius, - focused_color=focused_color, - focused_bgcolor=focused_bgcolor, - focused_border_width=focused_border_width, - focused_border_color=focused_border_color, - content_padding=content_padding, - dense=dense, - filled=filled, - prefix=prefix, - suffix=suffix, - fit_parent_size=fit_parent_size, - # - # TextField - # - value=value, - keyboard_type=keyboard_type, - rtl=rtl, - multiline=multiline, - min_lines=min_lines, - max_lines=max_lines, - max_length=max_length, - password=password, - can_reveal_password=can_reveal_password, - read_only=read_only, - shift_enter=shift_enter, - text_align=text_align, - autofocus=autofocus, - capitalization=capitalization, - autocorrect=autocorrect, - enable_suggestions=enable_suggestions, - smart_dashes_type=smart_dashes_type, - smart_quotes_type=smart_quotes_type, - cursor_color=cursor_color, - cursor_width=cursor_width, - cursor_height=cursor_height, - cursor_radius=cursor_radius, - show_cursor=show_cursor, - selection_color=selection_color, - input_filter=input_filter, - autofill_hints=autofill_hints, - enable_scribble=enable_scribble, - scroll_padding=scroll_padding, - obscuring_character=obscuring_character, - enable_interactive_selection=enable_interactive_selection, - enable_ime_personalized_learning=enable_ime_personalized_learning, - clip_behavior=clip_behavior, - keyboard_brightness=keyboard_brightness, - strut_style=strut_style, - animate_cursor_opacity=animate_cursor_opacity, - on_click=on_click, - on_change=on_change, - on_submit=on_submit, - on_focus=on_focus, - on_blur=on_blur, - on_tap_outside=on_tap_outside, - ) - - self.placeholder_text = placeholder_text - self.placeholder_style = placeholder_style - self.gradient = gradient - self.blend_mode = blend_mode - self.shadow = shadow - self.suffix_visibility_mode = suffix_visibility_mode - self.prefix_visibility_mode = prefix_visibility_mode - self.clear_button_semantics_label = clear_button_semantics_label - self.border = border - self.image = image - self.padding = padding - self.clear_button_visibility_mode = clear_button_visibility_mode - - def _get_control_name(self): - return "cupertinotextfield" - - def before_update(self): - super().before_update() - self._set_attr_json("gradient", self.__gradient) - self._set_attr_json("shadow", self.__shadow if self.__shadow else None) - self._set_attr_json("placeholderStyle", self.__placeholder_style) - self._set_attr_json("border", self.__border) - self._set_attr_json("image", self.__image) - self._set_attr_json("padding", self.__padding) - - # placeholder_text - @property - def placeholder_text(self): - return self._get_attr("placeholderText") - - @placeholder_text.setter - def placeholder_text(self, value): - self._set_attr("placeholderText", value) - - # placeholder_style - @property - def placeholder_style(self): - return self.__placeholder_style - - @placeholder_style.setter - def placeholder_style(self, value: Optional[TextStyle]): - self.__placeholder_style = value - - # gradient - @property - def gradient(self) -> Optional[Gradient]: - return self.__gradient - - @gradient.setter - def gradient(self, value: Optional[Gradient]): - self.__gradient = value - - # blend_mode - @property - def blend_mode(self) -> Optional[BlendMode]: - return self.__blend_mode - - @blend_mode.setter - def blend_mode(self, value: Optional[BlendMode]): - self.__blend_mode = value - self._set_enum_attr("blendMode", value, BlendMode) - - # shadow - @property - def shadow(self) -> Union[None, BoxShadow, List[BoxShadow]]: - return self.__shadow - - @shadow.setter - def shadow(self, value: Union[None, BoxShadow, List[BoxShadow]]): - self.__shadow = value if value is not None else [] - - # image - @property - def image(self) -> Optional[DecorationImage]: - return self.__image - - @image.setter - def image(self, value: Optional[DecorationImage]): - self.__image = value - - # suffix_visibility_mode - @property - def suffix_visibility_mode(self) -> Optional[VisibilityMode]: - return self.__suffix_visibility_mode - - @suffix_visibility_mode.setter - def suffix_visibility_mode(self, value: Optional[VisibilityMode]): - self.__suffix_visibility_mode = value - self._set_enum_attr("suffixVisibilityMode", value, VisibilityMode) - - # clear_button_visibility_mode - @property - def clear_button_visibility_mode(self) -> Optional[VisibilityMode]: - return self.__clear_button_visibility_mode - - @clear_button_visibility_mode.setter - def clear_button_visibility_mode(self, value: Optional[VisibilityMode]): - self.__clear_button_visibility_mode = value - self._set_enum_attr("clearButtonVisibilityMode", value, VisibilityMode) - - # prefix_visibility_mode - @property - def prefix_visibility_mode(self) -> Optional[VisibilityMode]: - return self.__prefix_visibility_mode - - @prefix_visibility_mode.setter - def prefix_visibility_mode(self, value: Optional[VisibilityMode]): - self.__prefix_visibility_mode = value - self._set_enum_attr("prefixVisibilityMode", value, VisibilityMode) - - # clear_button_semantics_label - @property - def clear_button_semantics_label(self) -> Optional[str]: - return self._get_attr("clearButtonSemanticsLabel") - - @clear_button_semantics_label.setter - def clear_button_semantics_label(self, value: Optional[str]): - self._set_attr("clearButtonSemanticsLabel", value) - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # border - @property - def border(self) -> Optional[Border]: - return self.__border - - @border.setter - def border(self, value: Optional[Border]): - self.__border = value diff --git a/sdk/python/packages/flet/src/flet/core/cupertino_timer_picker.py b/sdk/python/packages/flet/src/flet/core/cupertino_timer_picker.py deleted file mode 100644 index 5241f40f2..000000000 --- a/sdk/python/packages/flet/src/flet/core/cupertino_timer_picker.py +++ /dev/null @@ -1,201 +0,0 @@ -from enum import Enum -from typing import Any, Optional, Union - -from flet.core.alignment import Alignment -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class CupertinoTimerPickerMode(Enum): - HOUR_MINUTE = "hm" - HOUR_MINUTE_SECONDS = "hms" - MINUTE_SECONDS = "ms" - - -class CupertinoTimerPicker(ConstrainedControl): - """ - A countdown timer picker in iOS style. - - It can show a countdown duration with hour, minute and second spinners. The duration is bound between 0 and 23 hours 59 minutes 59 seconds. - - ----- - - Online docs: https://flet.dev/docs/controls/cupertinotimerpicker - """ - - def __init__( - self, - value: Optional[int] = None, - alignment: Optional[Alignment] = None, - second_interval: OptionalNumber = None, - minute_interval: OptionalNumber = None, - mode: Optional[CupertinoTimerPickerMode] = None, - bgcolor: Optional[ColorValue] = None, - item_extent: OptionalNumber = None, - on_change: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.value = value - self.alignment = alignment - self.mode = mode - self.bgcolor = bgcolor - self.on_change = on_change - self.second_interval = second_interval - self.minute_interval = minute_interval - self.item_extent = item_extent - - def _get_control_name(self): - return "cupertinotimerpicker" - - def before_update(self): - super().before_update() - self._set_attr_json("alignment", self.__alignment) - - # value - @property - def value(self) -> int: - return self._get_attr("value", data_type="int", def_value=0) - - @value.setter - def value(self, value: Optional[int]): - self._set_attr("value", value) - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # second_interval - @property - def second_interval(self) -> int: - return self._get_attr("secondInterval", data_type="int", def_value=1) - - @second_interval.setter - def second_interval(self, value: OptionalNumber): - self._set_attr("secondInterval", value) - - # item_extent - @property - def item_extent(self) -> float: - return self._get_attr("itemExtent", data_type="float", def_value=32.0) - - @item_extent.setter - def item_extent(self, value: OptionalNumber): - self._set_attr("itemExtent", value) - - # minute_interval - @property - def minute_interval(self) -> int: - return self._get_attr("minuteInterval", data_type="int", def_value=1) - - @minute_interval.setter - def minute_interval(self, value: OptionalNumber): - self._set_attr("minuteInterval", value) - - # mode - @property - def mode(self) -> Optional[CupertinoTimerPickerMode]: - return self.__mode - - @mode.setter - def mode(self, value: Optional[CupertinoTimerPickerMode]): - self.__mode = value - self._set_enum_attr("mode", value, CupertinoTimerPickerMode) - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) diff --git a/sdk/python/packages/flet/src/flet/core/datatable.py b/sdk/python/packages/flet/src/flet/core/datatable.py deleted file mode 100644 index f689f837e..000000000 --- a/sdk/python/packages/flet/src/flet/core/datatable.py +++ /dev/null @@ -1,769 +0,0 @@ -import json -from typing import Any, List, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.border import Border, BorderSide -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.gesture_detector import TapEvent -from flet.core.gradients import Gradient -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - BorderRadiusValue, - ClipBehavior, - ColorEnums, - ColorValue, - ControlStateValue, - MainAxisAlignment, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class DataColumnSortEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.column_index: int = d.get("i") - self.ascending: bool = d.get("a") - - -class DataColumn(Control): - def __init__( - self, - label: Control, - numeric: Optional[bool] = None, - tooltip: Optional[str] = None, - heading_row_alignment: Optional[MainAxisAlignment] = None, - on_sort: OptionalEventCallable[DataColumnSortEvent] = None, - # - # Control - # - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Control.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) - - self.__on_sort = EventHandler(lambda e: DataColumnSortEvent(e)) - self._add_event_handler("sort", self.__on_sort.get_handler()) - - self.label = label - self.numeric = numeric - self.tooltip = tooltip - self.heading_row_alignment = heading_row_alignment - self.on_sort = on_sort - - def _get_control_name(self): - return "datacolumn" - - def _get_children(self): - self.__label._set_attr_internal("n", "label") - return [self.__label] - - def before_update(self): - super().before_update() - assert self.__label.visible, "label must be visible" - - # label - @property - def label(self) -> Control: - return self.__label - - @label.setter - def label(self, value: Control): - self.__label = value - - # numeric - @property - def numeric(self) -> bool: - return self._get_attr("numeric", data_type="bool", def_value=False) - - @numeric.setter - def numeric(self, value: Optional[bool]): - self._set_attr("numeric", value) - - # tooltip - @property - def tooltip(self) -> Optional[str]: - return self._get_attr("tooltip") - - @tooltip.setter - def tooltip(self, value: Optional[str]): - self._set_attr("tooltip", value) - - # heading_row_alignment - @property - def heading_row_alignment(self) -> Optional[MainAxisAlignment]: - return self.__heading_row_alignment - - @heading_row_alignment.setter - def heading_row_alignment(self, value: Optional[MainAxisAlignment]): - self.__heading_row_alignment = value - self._set_enum_attr("headingRowAlignment", value, MainAxisAlignment) - - # on_sort - @property - def on_sort(self) -> OptionalEventCallable["DataColumnSortEvent"]: - return self.__on_sort.handler - - @on_sort.setter - def on_sort(self, handler: OptionalEventCallable["DataColumnSortEvent"]): - self.__on_sort.handler = handler - self._set_attr("onSort", True if handler is not None else None) - - -class DataCell(Control): - def __init__( - self, - content: Control, - placeholder: Optional[bool] = None, - show_edit_icon: Optional[bool] = None, - on_tap: OptionalControlEventCallable = None, - on_double_tap: OptionalControlEventCallable = None, - on_long_press: OptionalControlEventCallable = None, - on_tap_cancel: OptionalControlEventCallable = None, - on_tap_down: OptionalEventCallable[TapEvent] = None, - # - # Control - # - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Control.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) - - self.__on_tap_down = EventHandler(lambda e: TapEvent(e)) - self._add_event_handler("tap_down", self.__on_tap_down.get_handler()) - - self.content = content - self.on_double_tap = on_double_tap - self.on_long_press = on_long_press - self.on_tap = on_tap - self.on_tap_cancel = on_tap_cancel - self.on_tap_down = on_tap_down - self.placeholder = placeholder - self.show_edit_icon = show_edit_icon - - def _get_control_name(self): - return "datacell" - - def _get_children(self): - return [self.__content] - - def before_update(self): - super().before_update() - assert self.__content.visible, "content must be visible" - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # placeholder - @property - def placeholder(self) -> bool: - return self._get_attr("placeholder", data_type="bool", def_value=False) - - @placeholder.setter - def placeholder(self, value: Optional[bool]): - self._set_attr("placeholder", value) - - # show_edit_icon - @property - def show_edit_icon(self) -> bool: - return self._get_attr("showEditIcon", data_type="bool", def_value=False) - - @show_edit_icon.setter - def show_edit_icon(self, value: Optional[bool]): - self._set_attr("showEditIcon", value) - - # on_double_tap - @property - def on_double_tap(self) -> OptionalControlEventCallable: - return self._get_event_handler("double_tap") - - @on_double_tap.setter - def on_double_tap(self, handler: OptionalControlEventCallable): - self._add_event_handler("double_tap", handler) - self._set_attr("onDoubleTap", True if handler is not None else None) - - # on_long_press - @property - def on_long_press(self) -> OptionalControlEventCallable: - return self._get_event_handler("long_press") - - @on_long_press.setter - def on_long_press(self, handler: OptionalControlEventCallable): - self._add_event_handler("long_press", handler) - self._set_attr("onLongPress", True if handler is not None else None) - - # on_tap - @property - def on_tap(self) -> OptionalControlEventCallable: - return self._get_event_handler("tap") - - @on_tap.setter - def on_tap(self, handler: OptionalControlEventCallable): - self._add_event_handler("tap", handler) - self._set_attr("onTap", True if handler is not None else None) - - # on_tap_cancel - @property - def on_tap_cancel(self) -> OptionalControlEventCallable: - return self._get_event_handler("tap_cancel") - - @on_tap_cancel.setter - def on_tap_cancel(self, handler: OptionalControlEventCallable): - self._add_event_handler("tap_cancel", handler) - self._set_attr("onTapCancel", True if handler is not None else None) - - # on_tap_down - @property - def on_tap_down(self) -> OptionalEventCallable[TapEvent]: - return self.__on_tap_down.handler - - @on_tap_down.setter - def on_tap_down(self, handler: OptionalEventCallable[TapEvent]): - self.__on_tap_down.handler = handler - self._set_attr("onTapDown", True if handler is not None else None) - - -class DataRow(Control): - def __init__( - self, - cells: List[DataCell], - color: ControlStateValue[ColorValue] = None, - selected: Optional[bool] = None, - on_long_press: OptionalControlEventCallable = None, - on_select_changed: OptionalControlEventCallable = None, - # - # Control - # - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Control.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) - - self.cells = cells - self.color = color - self.selected = selected - self.on_long_press = on_long_press - self.on_select_changed = on_select_changed - - def _get_control_name(self): - return "datarow" - - def __contains__(self, item): - return item in self.__cells - - def before_update(self): - super().before_update() - assert any( - cell.visible for cell in self.__cells - ), "cells must contain at minimum one visible DataCell" - self._set_attr_json("color", self.__color, wrap_attr_dict=True) - - def _get_children(self): - return self.__cells - - # cells - @property - def cells(self) -> List[DataCell]: - return self.__cells - - @cells.setter - def cells(self, value: List[DataCell]): - assert all( - isinstance(cell, DataCell) for cell in value - ), "cells must contain only DataCell instances" - self.__cells = value - - # color - @property - def color(self) -> ControlStateValue[str]: - return self.__color - - @color.setter - def color(self, value: ControlStateValue[str]): - self.__color = value - - # selected - @property - def selected(self) -> bool: - return self._get_attr("selected", data_type="bool", def_value=False) - - @selected.setter - def selected(self, value: Optional[bool]): - self._set_attr("selected", value) - - # on_long_press - @property - def on_long_press(self) -> OptionalControlEventCallable: - return self._get_event_handler("long_press") - - @on_long_press.setter - def on_long_press(self, handler: OptionalControlEventCallable): - self._add_event_handler("long_press", handler) - self._set_attr("onLongPress", True if handler is not None else None) - - # on_select_changed - @property - def on_select_changed(self) -> OptionalControlEventCallable: - return self._get_event_handler("select_changed") - - @on_select_changed.setter - def on_select_changed(self, handler: OptionalControlEventCallable): - self._add_event_handler("select_changed", handler) - self._set_attr("onSelectChanged", True if handler is not None else None) - - -class DataTable(ConstrainedControl): - def __init__( - self, - columns: List[DataColumn], - rows: Optional[List[DataRow]] = None, - sort_ascending: Optional[bool] = None, - show_checkbox_column: Optional[bool] = None, - sort_column_index: Optional[int] = None, - show_bottom_border: Optional[bool] = None, - border: Optional[Border] = None, - border_radius: Optional[BorderRadiusValue] = None, - horizontal_lines: Optional[BorderSide] = None, - vertical_lines: Optional[BorderSide] = None, - checkbox_horizontal_margin: OptionalNumber = None, - column_spacing: OptionalNumber = None, - data_row_color: ControlStateValue[ColorValue] = None, - data_row_min_height: OptionalNumber = None, - data_row_max_height: OptionalNumber = None, - data_text_style: Optional[TextStyle] = None, - bgcolor: Optional[ColorValue] = None, - gradient: Optional[Gradient] = None, - divider_thickness: OptionalNumber = None, - heading_row_color: ControlStateValue[ColorValue] = None, - heading_row_height: OptionalNumber = None, - heading_text_style: Optional[TextStyle] = None, - horizontal_margin: OptionalNumber = None, - clip_behavior: Optional[ClipBehavior] = None, - on_select_all: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.columns = columns - self.rows = rows - self.border = border - self.border_radius = border_radius - self.horizontal_lines = horizontal_lines - self.vertical_lines = vertical_lines - self.bgcolor = bgcolor - self.gradient = gradient - self.divider_thickness = divider_thickness - self.checkbox_horizontal_margin = checkbox_horizontal_margin - self.column_spacing = column_spacing - self.data_row_color = data_row_color - self.data_row_min_height = data_row_min_height - self.data_row_max_height = data_row_max_height - self.data_text_style = data_text_style - self.heading_row_color = heading_row_color - self.heading_row_height = heading_row_height - self.heading_text_style = heading_text_style - self.horizontal_margin = horizontal_margin - self.show_bottom_border = show_bottom_border - self.show_checkbox_column = show_checkbox_column - self.sort_ascending = sort_ascending - self.sort_column_index = sort_column_index - self.on_select_all = on_select_all - self.clip_behavior = clip_behavior - - def _get_control_name(self): - return "datatable" - - def __contains__(self, item): - return item in self.__columns or item in self.__rows - - def before_update(self): - super().before_update() - visible_columns = list(filter(lambda column: column.visible, self.__columns)) - visible_rows = list(filter(lambda row: row.visible, self.__rows)) - assert ( - len(visible_columns) > 0 - ), "columns must contain at minimum one visible DataColumn" - assert all( - len(list(filter(lambda c: c.visible, row.cells))) == len(visible_columns) - for row in visible_rows - ), f"each visible DataRow must contain exactly as many visible DataCells as there are visible DataColumns ({len(visible_columns)})" - assert ( - self.data_row_min_height is None - or self.data_row_max_height is None - or (self.data_row_min_height <= self.data_row_max_height) - ), "data_row_min_height must be less than or equal to data_row_max_height" - assert ( - self.divider_thickness is None or self.divider_thickness >= 0 - ), "divider_thickness must be greater than or equal to 0" - assert self.sort_column_index is None or ( - 0 <= self.sort_column_index < len(visible_columns) - ), f"sort_column_index must be greater than or equal to 0 and less than the number of columns ({len(visible_columns)})" - self._set_attr_json("border", self.__border) - self._set_attr_json("gradient", self.__gradient) - self._set_attr_json("borderRadius", self.__border_radius) - self._set_attr_json("horizontalLines", self.__horizontal_lines) - self._set_attr_json("verticalLines", self.__vertical_lines) - self._set_attr_json("dataRowColor", self.__data_row_color) - self._set_attr_json("headingRowColor", self.__heading_row_color) - self._set_attr_json("dataTextStyle", self.__data_text_style) - self._set_attr_json("headingTextStyle", self.__heading_text_style) - - def _get_children(self): - return self.__columns + self.__rows - - # columns - @property - def columns(self) -> List[DataColumn]: - return self.__columns - - @columns.setter - def columns(self, value: List[DataColumn]): - assert all( - isinstance(column, DataColumn) for column in value - ), "columns must contain only DataColumn instances" - self.__columns = value - - # rows - @property - def rows(self) -> Optional[List[DataRow]]: - return self.__rows - - @rows.setter - def rows(self, value: Optional[List[DataRow]]): - self.__rows = value if value is not None else [] - assert all( - isinstance(row, DataRow) for row in self.__rows - ), "rows must contain only DataRow instances" - - # border - @property - def border(self) -> Optional[Border]: - return self.__border - - @border.setter - def border(self, value: Optional[Border]): - self.__border = value - - # border_radius - @property - def border_radius(self) -> Optional[BorderRadiusValue]: - return self.__border_radius - - @border_radius.setter - def border_radius(self, value: Optional[BorderRadiusValue]): - self.__border_radius = value - - # horizontal_lines - @property - def horizontal_lines(self) -> Optional[BorderSide]: - return self.__horizontal_lines - - @horizontal_lines.setter - def horizontal_lines(self, value: Optional[BorderSide]): - self.__horizontal_lines = value - - # vertical_lines - @property - def vertical_lines(self) -> Optional[BorderSide]: - return self.__vertical_lines - - @vertical_lines.setter - def vertical_lines(self, value: Optional[BorderSide]): - self.__vertical_lines = value - - # checkbox_horizontal_margin - @property - def checkbox_horizontal_margin(self) -> OptionalNumber: - return self._get_attr("checkboxHorizontalMargin") - - @checkbox_horizontal_margin.setter - def checkbox_horizontal_margin(self, value: OptionalNumber): - self._set_attr("checkboxHorizontalMargin", value) - - # column_spacing - @property - def column_spacing(self) -> OptionalNumber: - return self._get_attr("columnSpacing") - - @column_spacing.setter - def column_spacing(self, value: OptionalNumber): - self._set_attr("columnSpacing", value) - - # divider_thickness - @property - def divider_thickness(self) -> float: - return self._get_attr("dividerThickness", data_type="float", def_value=1.0) - - @divider_thickness.setter - def divider_thickness(self, value: OptionalNumber): - self._set_attr("dividerThickness", value) - - # horizontal_margin - @property - def horizontal_margin(self) -> OptionalNumber: - return self._get_attr("horizontalMargin") - - @horizontal_margin.setter - def horizontal_margin(self, value: OptionalNumber): - self._set_attr("horizontalMargin", value) - - # data_row_color - @property - def data_row_color(self) -> ControlStateValue[str]: - return self.__data_row_color - - @data_row_color.setter - def data_row_color(self, value: ControlStateValue[str]): - self.__data_row_color = value - - # data_row_min_height - @property - def data_row_min_height(self) -> OptionalNumber: - return self._get_attr("dataRowMinHeight") - - @data_row_min_height.setter - def data_row_min_height(self, value: OptionalNumber): - self._set_attr("dataRowMinHeight", value) - - # data_row_max_height - @property - def data_row_max_height(self) -> OptionalNumber: - return self._get_attr("dataRowMaxHeight") - - @data_row_max_height.setter - def data_row_max_height(self, value: OptionalNumber): - self._set_attr("dataRowMaxHeight", value) - - # data_text_style - @property - def data_text_style(self) -> Optional[TextStyle]: - return self.__data_text_style - - @data_text_style.setter - def data_text_style(self, value: Optional[TextStyle]): - self.__data_text_style = value - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgColor", value, ColorEnums) - - # gradient - @property - def gradient(self) -> Optional[Gradient]: - return self.__gradient - - @gradient.setter - def gradient(self, value: Optional[Gradient]): - self.__gradient = value - - # heading_row_color - @property - def heading_row_color(self) -> ControlStateValue[str]: - return self.__heading_row_color - - @heading_row_color.setter - def heading_row_color(self, value: ControlStateValue[str]): - self.__heading_row_color = value - - # heading_row_height - @property - def heading_row_height(self) -> OptionalNumber: - return self._get_attr("headingRowHeight") - - @heading_row_height.setter - def heading_row_height(self, value: OptionalNumber): - self._set_attr("headingRowHeight", value) - - # heading_text_style - @property - def heading_text_style(self) -> Optional[TextStyle]: - return self.__heading_text_style - - @heading_text_style.setter - def heading_text_style(self, value: Optional[TextStyle]): - self.__heading_text_style = value - - # show_bottom_border - @property - def show_bottom_border(self) -> bool: - return self._get_attr("showBottomBorder", data_type="bool", def_value=False) - - @show_bottom_border.setter - def show_bottom_border(self, value: Optional[bool]): - self._set_attr("showBottomBorder", value) - - # show_checkbox_column - @property - def show_checkbox_column(self) -> bool: - return self._get_attr("showCheckboxColumn", data_type="bool", def_value=False) - - @show_checkbox_column.setter - def show_checkbox_column(self, value: Optional[bool]): - self._set_attr("showCheckboxColumn", value) - - # sort_ascending - @property - def sort_ascending(self) -> bool: - return self._get_attr("sortAscending", data_type="bool", def_value=False) - - @sort_ascending.setter - def sort_ascending(self, value: Optional[bool]): - self._set_attr("sortAscending", value) - - # sort_column_index - @property - def sort_column_index(self) -> Optional[int]: - return self._get_attr("sortColumnIndex") - - @sort_column_index.setter - def sort_column_index(self, value: Optional[int]): - self._set_attr("sortColumnIndex", value) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # on_select_all - @property - def on_select_all(self) -> OptionalControlEventCallable: - return self._get_event_handler("select_all") - - @on_select_all.setter - def on_select_all(self, handler: OptionalControlEventCallable): - self._add_event_handler("select_all", handler) - self._set_attr("onSelectAll", True if handler is not None else None) - - -class Item(Control): - def __init__(self, obj): - Control.__init__(self) - assert obj, "obj cannot be empty" - self.obj = obj - - def _set_attr(self, name, value, dirty=True): - if value is None: - return - - orig_val = self._get_attr(name) - if orig_val is not None: - if isinstance(orig_val, bool): - value = str(value).lower() == "true" - elif isinstance(orig_val, float): - value = float(str(value)) - - self._set_attr_internal(name, value, dirty=False) - if isinstance(self.obj, dict): - self.obj[name] = value - else: - setattr(self.obj, name, value) - - def _fetch_attrs(self): - # reflection - obj = self.obj if isinstance(self.obj, dict) else vars(self.obj) - - for name, val in obj.items(): - data_type = ( - type(val).__name__ if isinstance(val, (bool, float)) else "string" - ) - orig_val = self._get_attr(name, data_type=data_type) - - if val != orig_val: - self._set_attr_internal(name, val, dirty=True) - - def _get_control_name(self): - return "item" diff --git a/sdk/python/packages/flet/src/flet/core/date_picker.py b/sdk/python/packages/flet/src/flet/core/date_picker.py deleted file mode 100644 index 30ddde9ca..000000000 --- a/sdk/python/packages/flet/src/flet/core/date_picker.py +++ /dev/null @@ -1,375 +0,0 @@ -from datetime import datetime -from enum import Enum -from typing import Any, Optional, Union - -from flet.core.control import Control, OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.textfield import KeyboardType -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - DateTimeValue, - IconEnums, - IconValue, - OptionalControlEventCallable, - OptionalEventCallable, - ResponsiveNumber, -) - -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal - - -class DatePickerMode(Enum): - DAY = "day" - YEAR = "year" - - -class DatePickerEntryMode(Enum): - CALENDAR = "calendar" - INPUT = "input" - CALENDAR_ONLY = "calendarOnly" - INPUT_ONLY = "inputOnly" - - -class DatePickerEntryModeChangeEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - self.entry_mode: Optional[DatePickerEntryMode] = DatePickerEntryMode(e.data) - - -class DatePicker(Control): - """ - A Material-style date picker dialog. - - It is added to [`page.overlay`](page#overlay) and can be opened by setting `open=True` or by calling `Page.open()` method. - - Depending on the `date_picker_entry_mode`, it will show either a Calendar or an Input (TextField) for picking a date. - - Example: - ``` - import flet as ft - - - def main(page): - page.horizontal_alignment = ft.CrossAxisAlignment.CENTER - - def handle_date_change(e: ft.ControlEvent): - page.add(ft.Text(f"Date changed: {e.control.value.strftime('%Y-%m-%d %H:%M %p')}")) - - cupertino_date_picker = ft.CupertinoDatePicker( - date_picker_mode=ft.CupertinoDatePickerMode.DATE_AND_TIME, - on_change=handle_date_change, - ) - page.add( - ft.CupertinoFilledButton( - "Open CupertinoDatePicker", - on_click=lambda e: page.open( - ft.CupertinoBottomSheet( - cupertino_date_picker, - height=216, - padding=ft.padding.only(top=6), - ) - ), - ) - ) - - - ft.app(main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/datepicker - """ - - def __init__( - self, - open: bool = False, - value: Optional[DateTimeValue] = None, - first_date: DateTimeValue = datetime(year=1900, month=1, day=1), - last_date: DateTimeValue = datetime(year=2050, month=1, day=1), - current_date: DateTimeValue = datetime.now(), - keyboard_type: Optional[KeyboardType] = None, - date_picker_mode: Optional[DatePickerMode] = None, - date_picker_entry_mode: Optional[DatePickerEntryMode] = None, - help_text: Optional[str] = None, - cancel_text: Optional[str] = None, - confirm_text: Optional[str] = None, - error_format_text: Optional[str] = None, - error_invalid_text: Optional[str] = None, - field_hint_text: Optional[str] = None, - field_label_text: Optional[str] = None, - switch_to_calendar_icon: Optional[IconValue] = None, - switch_to_input_icon: Optional[IconValue] = None, - barrier_color: Optional[ColorValue] = None, - on_change: OptionalControlEventCallable = None, - on_dismiss: OptionalControlEventCallable = None, - on_entry_mode_change: OptionalEventCallable[ - "DatePickerEntryModeChangeEvent" - ] = None, - # - # Control - # - ref: Optional[Ref] = None, - expand: Optional[Union[bool, int]] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - tooltip: Optional[TooltipValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - tooltip=tooltip, - visible=visible, - disabled=disabled, - data=data, - ) - - self.__on_entry_mode_change = EventHandler( - lambda e: DatePickerEntryModeChangeEvent(e) - ) - self._add_event_handler( - "entryModeChange", self.__on_entry_mode_change.get_handler() - ) - - self.value = value - self.first_date = first_date - self.last_date = last_date - self.current_date = current_date - self.keyboard_type = keyboard_type - self.help_text = help_text - self.cancel_text = cancel_text - self.confirm_text = confirm_text - self.error_format_text = error_format_text - self.error_invalid_text = error_invalid_text - self.date_picker_mode = date_picker_mode - self.date_picker_entry_mode = date_picker_entry_mode - self.field_hint_text = field_hint_text - self.field_label_text = field_label_text - self.switch_to_calendar_icon = switch_to_calendar_icon - self.switch_to_input_icon = switch_to_input_icon - self.on_change = on_change - self.on_dismiss = on_dismiss - self.open = open - self.on_entry_mode_change = on_entry_mode_change - self.barrier_color = barrier_color - - def _get_control_name(self): - return "datepicker" - - # open - @property - def open(self) -> bool: - return self._get_attr("open", data_type="bool", def_value=False) - - @open.setter - def open(self, value: Optional[bool]): - self._set_attr("open", value) - - # value - @property - def value(self) -> Optional[DateTimeValue]: - v = self._get_attr("value") - return datetime.fromisoformat(v) if v else None - - @value.setter - def value(self, value: Optional[DateTimeValue]): - self.__value = value - self._set_attr("value", value if value is None else value.isoformat()) - - @property - def first_date(self) -> DateTimeValue: - return self.__first_date - - @first_date.setter - def first_date(self, value: DateTimeValue): - self.__first_date = value - self._set_attr("firstDate", value.isoformat()) - - # last_date - @property - def last_date(self) -> DateTimeValue: - return self.__last_date - - @last_date.setter - def last_date(self, value: DateTimeValue): - self.__last_date = value - self._set_attr("lastDate", value.isoformat()) - - # current_date - @property - def current_date(self) -> DateTimeValue: - return self.__current_date - - @current_date.setter - def current_date(self, value: DateTimeValue): - self.__current_date = value - self._set_attr("currentDate", value.isoformat()) - - # field_hint_text - @property - def field_hint_text(self) -> Optional[str]: - return self._get_attr("fieldHintText") - - @field_hint_text.setter - def field_hint_text(self, value: Optional[str]): - self._set_attr("fieldHintText", value) - - # field_label_text - @property - def field_label_text(self) -> Optional[str]: - return self._get_attr("fieldLabelText") - - @field_label_text.setter - def field_label_text(self, value: Optional[str]): - self._set_attr("fieldLabelText", value) - - # help_text - @property - def help_text(self) -> Optional[str]: - return self._get_attr("helpText") - - @help_text.setter - def help_text(self, value: Optional[str]): - self._set_attr("helpText", value) - - # cancel_text - @property - def cancel_text(self) -> Optional[str]: - return self._get_attr("cancelText") - - @cancel_text.setter - def cancel_text(self, value: Optional[str]): - self._set_attr("cancelText", value) - - # confirm_text - @property - def confirm_text(self) -> Optional[str]: - return self._get_attr("confirmText") - - @confirm_text.setter - def confirm_text(self, value: Optional[str]): - self._set_attr("confirmText", value) - - # error_format_text - @property - def error_format_text(self) -> Optional[str]: - return self._get_attr("errorFormatText") - - @error_format_text.setter - def error_format_text(self, value: Optional[str]): - self._set_attr("errorFormatText", value) - - # error_invalid_text - @property - def error_invalid_text(self) -> Optional[str]: - return self._get_attr("errorInvalidText") - - @error_invalid_text.setter - def error_invalid_text(self, value: Optional[str]): - self._set_attr("errorInvalidText", value) - - # keyboard_type - @property - def keyboard_type(self) -> Optional[KeyboardType]: - return self.__keyboard_type - - @keyboard_type.setter - def keyboard_type(self, value: Optional[KeyboardType]): - self.__keyboard_type = value - self._set_enum_attr("keyboardType", value, KeyboardType) - - # date_picker_mode - @property - def date_picker_mode(self) -> Optional[DatePickerMode]: - return self.__date_picker_mode - - @date_picker_mode.setter - def date_picker_mode(self, value: Optional[DatePickerMode]): - self.__date_picker_mode = value - self._set_enum_attr("datePickerMode", value, DatePickerMode) - - # date_picker_entry_mode - @property - def date_picker_entry_mode(self) -> Optional[DatePickerEntryMode]: - return self.__date_picker_entry_mode - - @date_picker_entry_mode.setter - def date_picker_entry_mode(self, value: Optional[DatePickerEntryMode]): - self.__date_picker_entry_mode = value - self._set_enum_attr("datePickerEntryMode", value, DatePickerEntryMode) - - # switch_to_calendar_icon - @property - def switch_to_calendar_icon(self) -> Optional[IconValue]: - return self.__switch_to_calendar_icon - - @switch_to_calendar_icon.setter - def switch_to_calendar_icon(self, value: Optional[IconValue]): - self.__switch_to_calendar_icon = value - self._set_enum_attr("switchToCalendarEntryModeIcon", value, IconEnums) - - # switch_to_input_icon - @property - def switch_to_input_icon(self) -> Optional[IconValue]: - return self.__switch_to_input_icon - - @switch_to_input_icon.setter - def switch_to_input_icon(self, value: Optional[IconValue]): - self.__switch_to_input_icon = value - self._set_enum_attr("switchToInputEntryModeIcon", value, IconEnums) - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - - # on_dismiss - @property - def on_dismiss(self) -> OptionalControlEventCallable: - return self._get_event_handler("dismiss") - - @on_dismiss.setter - def on_dismiss(self, handler: OptionalControlEventCallable): - self._add_event_handler("dismiss", handler) - - # on_entry_mode_change - @property - def on_entry_mode_change( - self, - ) -> OptionalEventCallable[DatePickerEntryModeChangeEvent]: - return self.__on_entry_mode_change.handler - - @on_entry_mode_change.setter - def on_entry_mode_change( - self, handler: OptionalEventCallable[DatePickerEntryModeChangeEvent] - ): - self.__on_entry_mode_change.handler = handler - - # barrier_color - @property - def barrier_color(self) -> Optional[ColorValue]: - return self.__barrier_color - - @barrier_color.setter - def barrier_color(self, value: Optional[ColorValue]): - self.__barrier_color = value - self._set_enum_attr("barrierColor", value, ColorEnums) diff --git a/sdk/python/packages/flet/src/flet/core/dismissible.py b/sdk/python/packages/flet/src/flet/core/dismissible.py deleted file mode 100644 index 7e281a80e..000000000 --- a/sdk/python/packages/flet/src/flet/core/dismissible.py +++ /dev/null @@ -1,297 +0,0 @@ -import json -from typing import Any, Dict, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.snack_bar import DismissDirection -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class Dismissible(ConstrainedControl, AdaptiveControl): - """ - A control that can be dismissed by dragging in the indicated `dismiss_direction`. When dragged or flung in the - specified `dismiss_direction`, it's content smoothly slides out of view. - - After completing the sliding animation, if a `resize_duration` is provided, this control further animates its - height (or width, depending on what is perpendicular to the `dismiss_direction`), gradually reducing it to zero - over the specified `resize_duration`. - - ------- - - Online Docs: https://flet.dev/docs/controls/dismissible - """ - - def __init__( - self, - content: Control, - background: Optional[Control] = None, - secondary_background: Optional[Control] = None, - dismiss_direction: Optional[DismissDirection] = None, - dismiss_thresholds: Optional[Dict[DismissDirection, OptionalNumber]] = None, - movement_duration: Optional[int] = None, - resize_duration: Optional[int] = None, - cross_axis_end_offset: OptionalNumber = None, - on_update: OptionalEventCallable["DismissibleUpdateEvent"] = None, - on_dismiss: OptionalEventCallable["DismissibleDismissEvent"] = None, - on_confirm_dismiss: OptionalEventCallable["DismissibleDismissEvent"] = None, - on_resize: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - key: Optional[str] = None, - # - # Adaptive - # - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.__on_dismiss = EventHandler(lambda e: DismissibleDismissEvent(e)) - self.__on_update = EventHandler(lambda e: DismissibleUpdateEvent(e)) - self.__on_confirm_dismiss = EventHandler(lambda e: DismissibleDismissEvent(e)) - - self._add_event_handler("dismiss", self.__on_dismiss.get_handler()) - self._add_event_handler("update", self.__on_update.get_handler()) - self._add_event_handler( - "confirm_dismiss", self.__on_confirm_dismiss.get_handler() - ) - - self.content = content - self.background = background - self.secondary_background = secondary_background - self.dismiss_direction = dismiss_direction - self.dismiss_thresholds = dismiss_thresholds - self.movement_duration = movement_duration - self.resize_duration = resize_duration - self.cross_axis_end_offset = cross_axis_end_offset - self.on_update = on_update - self.on_dismiss = on_dismiss - self.on_confirm_dismiss = on_confirm_dismiss - self.on_resize = on_resize - - def _get_control_name(self): - return "dismissible" - - def _get_children(self): - self.__content._set_attr_internal("n", "content") - children = [self.__content] - if self.__background: - self.__background._set_attr_internal("n", "background") - children.append(self.__background) - if self.__secondary_background: - self.__secondary_background._set_attr_internal("n", "secondaryBackground") - children.append(self.__secondary_background) - return children - - def before_update(self): - super().before_update() - self._set_attr_json("dismissThresholds", self.__dismiss_thresholds) - - def confirm_dismiss(self, dismiss: bool): - self.invoke_method("confirm_dismiss", {"dismiss": str(dismiss).lower()}) - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # background - @property - def background(self) -> Optional[Control]: - return self.__background - - @background.setter - def background(self, value: Optional[Control]): - self.__background = value - - # secondary_background - @property - def secondary_background(self) -> Optional[Control]: - return self.__secondary_background - - @secondary_background.setter - def secondary_background(self, value: Optional[Control]): - self.__secondary_background = value - - # movementDuration - @property - def movement_duration(self) -> Optional[int]: - return self._get_attr("movementDuration", data_type="int") - - @movement_duration.setter - def movement_duration(self, value: Optional[int]): - self._set_attr("movementDuration", value) - - # resizeDuration - @property - def resize_duration(self) -> Optional[int]: - return self._get_attr("resizeDuration", data_type="int") - - @resize_duration.setter - def resize_duration(self, value: Optional[int]): - self._set_attr("resizeDuration", value) - - # crossAxisEndOffset - @property - def cross_axis_end_offset(self) -> OptionalNumber: - return self._get_attr("crossAxisEndOffset", data_type="float") - - @cross_axis_end_offset.setter - def cross_axis_end_offset(self, value: OptionalNumber): - self._set_attr("crossAxisEndOffset", value) - - # dismissDirection - @property - def dismiss_direction(self) -> Optional[DismissDirection]: - return self.__dismiss_direction - - @dismiss_direction.setter - def dismiss_direction(self, value: Optional[DismissDirection]): - self.__dismiss_direction = value - self._set_enum_attr("dismissDirection", value, DismissDirection) - - # dismissThresholds - @property - def dismiss_thresholds(self) -> Optional[Dict[DismissDirection, OptionalNumber]]: - return self.__dismiss_thresholds - - @dismiss_thresholds.setter - def dismiss_thresholds( - self, value: Optional[Dict[DismissDirection, OptionalNumber]] - ): - self.__dismiss_thresholds = value - - # on_dismiss - @property - def on_dismiss(self) -> OptionalEventCallable["DismissibleDismissEvent"]: - return self.__on_dismiss.handler - - @on_dismiss.setter - def on_dismiss(self, handler: OptionalEventCallable["DismissibleDismissEvent"]): - self.__on_dismiss.handler = handler - self._set_attr("onDismiss", True if handler is not None else None) - - # on_confirm_dismiss - @property - def on_confirm_dismiss(self) -> OptionalEventCallable["DismissibleDismissEvent"]: - return self.__on_confirm_dismiss.handler - - @on_confirm_dismiss.setter - def on_confirm_dismiss( - self, handler: OptionalEventCallable["DismissibleDismissEvent"] - ): - self.__on_confirm_dismiss.handler = handler - self._set_attr("onConfirmDismiss", True if handler is not None else None) - - # on_update - @property - def on_update(self) -> OptionalEventCallable["DismissibleUpdateEvent"]: - return self.__on_update.handler - - @on_update.setter - def on_update(self, handler: OptionalEventCallable["DismissibleUpdateEvent"]): - self.__on_update.handler = handler - self._set_attr("onUpdate", True if handler is not None else None) - - # on_resize - @property - def on_resize(self): - return self._get_event_handler("resize") - - @on_resize.setter - def on_resize(self, handler: OptionalControlEventCallable): - self._add_event_handler("resize", handler) - self._set_attr("onResize", True if handler is not None else None) - - -class DismissibleDismissEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - self.direction = DismissDirection(e.data) - - -class DismissibleUpdateEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.direction: DismissDirection = DismissDirection(d.get("direction")) - self.progress: float = d.get("progress") - self.reached: bool = d.get("reached") - self.previous_reached: bool = d.get("previous_reached") diff --git a/sdk/python/packages/flet/src/flet/core/divider.py b/sdk/python/packages/flet/src/flet/core/divider.py deleted file mode 100644 index 4c2a8d4c5..000000000 --- a/sdk/python/packages/flet/src/flet/core/divider.py +++ /dev/null @@ -1,136 +0,0 @@ -from typing import Any, Optional - -from flet.core.badge import BadgeValue -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ColorEnums, ColorValue - - -class Divider(Control): - """ - A thin horizontal line, with padding on either side. - - In the material design language, this represents a divider. - - Example: - ``` - import flet as ft - - - def main(page: ft.Page): - - page.add( - ft.Column( - [ - ft.Container( - bgcolor=ft.colors.AMBER, - alignment=ft.alignment.center, - expand=True, - ), - ft.Divider(), - ft.Container( - bgcolor=ft.colors.PINK, alignment=ft.alignment.center, expand=True - ), - ], - spacing=0, - expand=True, - ), - ) - - - ft.app(target=main) - - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/divider - """ - - def __init__( - self, - height: OptionalNumber = None, - thickness: OptionalNumber = None, - color: Optional[ColorValue] = None, - leading_indent: OptionalNumber = None, - trailing_indent: OptionalNumber = None, - # - # Control - # - ref: Optional[Ref] = None, - opacity: OptionalNumber = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - opacity=opacity, - tooltip=tooltip, - badge=badge, - visible=visible, - data=data, - ) - - self.height = height - self.thickness = thickness - self.color = color - self.leading_indent = leading_indent - self.trailing_indent = trailing_indent - - def _get_control_name(self): - return "divider" - - # height - @property - def height(self) -> OptionalNumber: - return self._get_attr("height", data_type="float") - - @height.setter - def height(self, value: OptionalNumber): - assert value is None or value >= 0, "height cannot be negative" - self._set_attr("height", value) - - # thickness - @property - def thickness(self) -> OptionalNumber: - return self._get_attr("thickness", data_type="float") - - @thickness.setter - def thickness(self, value: OptionalNumber): - assert value is None or value >= 0, "thickness cannot be negative" - self._set_attr("thickness", value) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # leading_indent - @property - def leading_indent(self) -> OptionalNumber: - return self._get_attr("leadingIndent", data_type="float") - - @leading_indent.setter - def leading_indent(self, value: OptionalNumber): - assert value is None or value >= 0, "leading_indent cannot be negative" - self._set_attr("leadingIndent", value) - - # trailing_indent - @property - def trailing_indent(self) -> OptionalNumber: - return self._get_attr("trailingIndent", data_type="float") - - @trailing_indent.setter - def trailing_indent(self, value: OptionalNumber): - assert value is None or value >= 0, "trailing_indent cannot be negative" - self._set_attr("trailingIndent", value) diff --git a/sdk/python/packages/flet/src/flet/core/drag_target.py b/sdk/python/packages/flet/src/flet/core/drag_target.py deleted file mode 100644 index 549427902..000000000 --- a/sdk/python/packages/flet/src/flet/core/drag_target.py +++ /dev/null @@ -1,214 +0,0 @@ -import json -from typing import Any, Optional - -from flet.core.control import Control -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.types import OptionalControlEventCallable, OptionalEventCallable - - -class DragTarget(Control): - """ - A control that completes drag operation when a `Draggable` widget is dropped. - - When a draggable is dragged on top of a drag target, the drag target is asked whether it will accept the data the draggable is carrying. The drag target will accept incoming drag if it belongs to the same group as draggable. If the user does drop the draggable on top of the drag target (and the drag target has indicated that it will accept the draggable's data), then the drag target is asked to accept the draggable's data. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.title = "Drag and Drop example" - - def drag_will_accept(e): - e.control.content.border = ft.border.all( - 2, ft.colors.BLACK45 if e.data == "true" else ft.colors.RED - ) - e.control.update() - - def drag_accept(e: ft.DragTargetEvent): - src = page.get_control(e.src_id) - e.control.content.bgcolor = src.content.bgcolor - e.control.content.border = None - e.control.update() - - def drag_leave(e): - e.control.content.border = None - e.control.update() - - page.add( - ft.Row( - [ - ft.Column( - [ - ft.Draggable( - group="color", - content=ft.Container( - width=50, - height=50, - bgcolor=ft.colors.CYAN, - border_radius=5, - ), - content_feedback=ft.Container( - width=20, - height=20, - bgcolor=ft.colors.CYAN, - border_radius=3, - ), - ), - ft.Draggable( - group="color", - content=ft.Container( - width=50, - height=50, - bgcolor=ft.colors.YELLOW, - border_radius=5, - ), - ), - ft.Draggable( - group="color1", - content=ft.Container( - width=50, - height=50, - bgcolor=ft.colors.GREEN, - border_radius=5, - ), - ), - ] - ), - ft.Container(width=100), - ft.DragTarget( - group="color", - content=ft.Container( - width=50, - height=50, - bgcolor=ft.colors.BLUE_GREY_100, - border_radius=5, - ), - on_will_accept=drag_will_accept, - on_accept=drag_accept, - on_leave=drag_leave, - ), - ] - ) - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/dragtarget - """ - - def __init__( - self, - content: Control, - group: Optional[str] = None, - on_will_accept: OptionalControlEventCallable = None, - on_accept: OptionalEventCallable["DragTargetEvent"] = None, - on_leave: OptionalControlEventCallable = None, - on_move: OptionalEventCallable["DragTargetEvent"] = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - self.__on_accept = EventHandler(lambda e: DragTargetEvent(e)) - self.__on_move = EventHandler(lambda e: DragTargetEvent(e)) - self._add_event_handler("accept", self.__on_accept.get_handler()) - self._add_event_handler("move", self.__on_move.get_handler()) - - self.group = group - self.content = content - self.on_will_accept = on_will_accept - self.on_accept = on_accept - self.on_leave = on_leave - self.on_move = on_move - - def _get_control_name(self): - return "dragtarget" - - def _get_children(self): - self.__content._set_attr_internal("n", "content") - return [self.__content] - - def before_update(self): - super().before_update() - assert self.__content.visible, "content must be visible" - - # group - @property - def group(self) -> Optional[str]: - return self._get_attr("group") - - @group.setter - def group(self, value: Optional[str]): - self._set_attr("group", value) - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # on_will_accept - @property - def on_will_accept(self) -> OptionalControlEventCallable: - return self._get_event_handler("will_accept") - - @on_will_accept.setter - def on_will_accept(self, handler: OptionalControlEventCallable): - self._add_event_handler("will_accept", handler) - - # on_accept - @property - def on_accept(self) -> OptionalEventCallable["DragTargetEvent"]: - return self.__on_accept.handler - - @on_accept.setter - def on_accept(self, handler: OptionalEventCallable["DragTargetEvent"]): - self.__on_accept.handler = handler - - # on_leave - @property - def on_leave(self) -> OptionalControlEventCallable: - return self._get_event_handler("leave") - - @on_leave.setter - def on_leave(self, handler: OptionalControlEventCallable): - self._add_event_handler("leave", handler) - - # on_move - @property - def on_move(self) -> OptionalEventCallable["DragTargetEvent"]: - return self.__on_move.handler - - @on_move.setter - def on_move(self, handler: OptionalEventCallable["DragTargetEvent"]): - self.__on_move.handler = handler - - -class DragTargetEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.src_id: float = d.get("src_id") - self.x: float = d.get("x") - self.y: float = d.get("y") diff --git a/sdk/python/packages/flet/src/flet/core/draggable.py b/sdk/python/packages/flet/src/flet/core/draggable.py deleted file mode 100644 index c5f565ffc..000000000 --- a/sdk/python/packages/flet/src/flet/core/draggable.py +++ /dev/null @@ -1,255 +0,0 @@ -from typing import Any, Optional - -from flet.core.alignment import Axis -from flet.core.control import Control -from flet.core.ref import Ref -from flet.core.types import OptionalControlEventCallable - - -class Draggable(Control): - """ - A control that can be dragged from to a `DragTarget`. - - When a draggable control recognizes the start of a drag gesture, it displays a `content_feedback` control that tracks the user's finger across the screen. If the user lifts their finger while on top of a `DragTarget`, that target is given the opportunity to complete drag-and-drop flow. - - Example: - ``` - import flet - from flet import ( - Column, - Container, - Draggable, - DragTarget, - DragTargetAcceptEvent, - Page, - Row, - border, - colors, - ) - - - def main(page: Page): - page.title = "Drag and Drop example" - - def drag_will_accept(e): - e.control.content.border = border.all( - 2, colors.BLACK45 if e.data == "true" else colors.RED - ) - e.control.update() - - def drag_accept(e: DragTargetAcceptEvent): - src = page.get_control(e.src_id) - e.control.content.bgcolor = src.content.bgcolor - e.control.content.border = None - e.control.update() - - def drag_leave(e): - e.control.content.border = None - e.control.update() - - page.add( - Row( - [ - Column( - [ - Draggable( - group="color", - content=Container( - width=50, - height=50, - bgcolor=colors.CYAN, - border_radius=5, - ), - content_feedback=Container( - width=20, - height=20, - bgcolor=colors.CYAN, - border_radius=3, - ), - ), - Draggable( - group="color", - content=Container( - width=50, - height=50, - bgcolor=colors.YELLOW, - border_radius=5, - ), - ), - Draggable( - group="color1", - content=Container( - width=50, - height=50, - bgcolor=colors.GREEN, - border_radius=5, - ), - ), - ] - ), - Container(width=100), - DragTarget( - group="color", - content=Container( - width=50, - height=50, - bgcolor=colors.BLUE_GREY_100, - border_radius=5, - ), - on_will_accept=drag_will_accept, - on_accept=drag_accept, - on_leave=drag_leave, - ), - ] - ) - ) - - - flet.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/draggable - """ - - def __init__( - self, - content: Control, - group: Optional[str] = None, - content_when_dragging: Optional[Control] = None, - content_feedback: Optional[Control] = None, - axis: Optional[Axis] = None, - affinity: Optional[Axis] = None, - max_simultaneous_drags: Optional[int] = None, - on_drag_start: OptionalControlEventCallable = None, - on_drag_complete: OptionalControlEventCallable = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - self.group = group - self.content = content - self.content_when_dragging = content_when_dragging - self.content_feedback = content_feedback - self.on_drag_start = on_drag_start - self.on_drag_complete = on_drag_complete - self.axis = axis - self.affinity = affinity - self.max_simultaneous_drags = max_simultaneous_drags - - def _get_control_name(self): - return "draggable" - - def _get_children(self): - self.__content._set_attr_internal("n", "content") - children = [self.__content] - if self.__content_when_dragging: - self.__content_when_dragging._set_attr_internal( - "n", "content_when_dragging" - ) - children.append(self.__content_when_dragging) - if self.__content_feedback: - self.__content_feedback._set_attr_internal("n", "content_feedback") - children.append(self.__content_feedback) - return children - - def before_update(self): - super().before_update() - assert self.__content.visible, "content must be visible" - - # group - @property - def group(self) -> Optional[str]: - return self._get_attr("group") - - @group.setter - def group(self, value: Optional[str]): - self._set_attr("group", value) - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # content_when_dragging - @property - def content_when_dragging(self) -> Optional[Control]: - return self.__content_when_dragging - - @content_when_dragging.setter - def content_when_dragging(self, value: Optional[Control]): - self.__content_when_dragging = value - - # content_feedback - @property - def content_feedback(self) -> Optional[Control]: - return self.__content_feedback - - @content_feedback.setter - def content_feedback(self, value: Optional[Control]): - self.__content_feedback = value - - # max_simultaneous_drags - @property - def max_simultaneous_drags(self) -> Optional[int]: - return self._get_attr("maxSimultaneousDrags") - - @max_simultaneous_drags.setter - def max_simultaneous_drags(self, value: Optional[int]): - self._set_attr("maxSimultaneousDrags", value) - - # axis - @property - def axis(self) -> Optional[Axis]: - return self.__axis - - @axis.setter - def axis(self, value: Optional[Axis]): - self.__axis = value - self._set_enum_attr("axis", value, Axis) - - # affinity - @property - def affinity(self) -> Optional[Axis]: - return self.__affinity - - @affinity.setter - def affinity(self, value: Optional[Axis]): - self.__affinity = value - self._set_enum_attr("affinity", value, Axis) - - # on_drag_start - @property - def on_drag_start(self) -> OptionalControlEventCallable: - return self._get_event_handler("dragStart") - - @on_drag_start.setter - def on_drag_start(self, handler: OptionalControlEventCallable): - self._add_event_handler("dragStart", handler) - - # on_drag_complete - @property - def on_drag_complete(self) -> OptionalControlEventCallable: - return self._get_event_handler("dragComplete") - - @on_drag_complete.setter - def on_drag_complete(self, handler: OptionalControlEventCallable): - self._add_event_handler("dragComplete", handler) diff --git a/sdk/python/packages/flet/src/flet/core/dropdown.py b/sdk/python/packages/flet/src/flet/core/dropdown.py deleted file mode 100644 index 5ea85160b..000000000 --- a/sdk/python/packages/flet/src/flet/core/dropdown.py +++ /dev/null @@ -1,600 +0,0 @@ -import time -import warnings -from typing import Any, Dict, List, Optional, Union - -from flet.core.alignment import Alignment -from flet.core.animation import AnimationValue -from flet.core.buttons import ButtonStyle -from flet.core.control import Control, OptionalNumber -from flet.core.form_field_control import FormFieldControl, InputBorder -from flet.core.menu_bar import MenuStyle -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.textfield import InputFilter, TextCapitalization -from flet.core.types import ( - BorderRadiusValue, - ColorValue, - ControlState, - ControlStateValue, - IconEnums, - IconValueOrControl, - Number, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, - TextAlign, -) - - -class Option(Control): - def __init__( - self, - key: Optional[str] = None, - text: Optional[str] = None, - content: Optional[Control] = None, - leading_icon: Optional[IconValueOrControl] = None, - trailing_icon: Optional[IconValueOrControl] = None, - style: Optional[ButtonStyle] = None, - alignment: Optional[Alignment] = None, # to be deprecated - text_style: Optional[TextStyle] = None, # to be deprecated - on_click: OptionalControlEventCallable = None, # to be deprecated - # - # Control - # - ref=None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - Control.__init__(self, ref=ref, disabled=disabled, visible=visible, data=data) - self.key = key - self.text = text - self.content = content - self.leading_icon = leading_icon - self.trailing_icon = trailing_icon - self.style = style - - deprecated_properties_list = ["text_style", "on_click", "alignment"] - - for item in deprecated_properties_list: - if eval(item) is not None: - warnings.warn( - f"{item} is deprecated since version 0.27.0 " - f"and will be removed in version 0.30.0.", - category=DeprecationWarning, - stacklevel=2, - ) - - def _get_control_name(self): - return "dropdownoption" - - def _get_children(self): - children = super()._get_children() - if isinstance(self.__leading_icon, Control): - self.__leading_icon._set_attr_internal("n", "leadingIcon") - children.append(self.__leading_icon) - if isinstance(self.__trailing_icon, Control): - self.__trailing_icon._set_attr_internal("n", "trailing_icon") - children.append(self.__trailing_icon) - if isinstance(self.__content, Control): - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - return children - - def before_update(self): - super().before_update() - assert ( - self.key is not None or self.text is not None - ), "key or text must be specified" - self._set_attr_json("style", self.__style) - - # key - @property - def key(self) -> Optional[str]: - return self._get_attr("key") - - @key.setter - def key(self, value: Optional[str]): - self._set_attr("key", value) - - # text - @property - def text(self) -> Optional[str]: - return self._get_attr("text") - - @text.setter - def text(self, value: Optional[str]): - self._set_attr("text", value) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # style - @property - def style(self) -> Optional[ButtonStyle]: - return self.__style - - @style.setter - def style(self, value: Optional[ButtonStyle]): - self.__style = value - - # leading_icon - @property - def leading_icon(self) -> Optional[IconValueOrControl]: - return self.__leading_icon - - @leading_icon.setter - def leading_icon(self, value: Optional[IconValueOrControl]): - self.__leading_icon = value - if not isinstance(value, Control): - self._set_enum_attr("leadingIcon", value, IconEnums) - - # trailing_icon - @property - def trailing_icon(self) -> Optional[IconValueOrControl]: - return self.__trailing_icon - - @trailing_icon.setter - def trailing_icon(self, value: Optional[IconValueOrControl]): - self.__trailing_icon = value - if not isinstance(value, Control): - self._set_enum_attr("trailingIcon", value, IconEnums) - - -DropdownOption = Option - - -class Dropdown(FormFieldControl): - """ - A dropdown control that allows users to select a single option from a list of options. - ----- - Online docs: https://flet.dev/docs/controls/dropdown - """ - - def __init__( - self, - value: Optional[str] = None, - autofocus: Optional[bool] = None, - text_align: Optional[TextAlign] = None, - elevation: ControlStateValue[OptionalNumber] = None, - options: Optional[List[Option]] = None, - label_content: Optional[str] = None, - enable_filter: Optional[bool] = None, - enable_search: Optional[bool] = None, - editable: Optional[bool] = None, - max_menu_height: OptionalNumber = None, # to be discontinued - menu_height: OptionalNumber = None, - menu_width: OptionalNumber = None, - expanded_insets: PaddingValue = None, - selected_suffix: Optional[Control] = None, - input_filter: Optional[InputFilter] = None, - capitalization: Optional[TextCapitalization] = None, - options_fill_horizontally: Optional[bool] = None, # to be deprecated - padding: Optional[PaddingValue] = None, # to be deprecated - trailing_icon: Optional[IconValueOrControl] = None, - leading_icon: Optional[IconValueOrControl] = None, - select_icon: Optional[IconValueOrControl] = None, # to be deprecated - selected_trailing_icon: Optional[IconValueOrControl] = None, - on_change: OptionalEventCallable = None, - on_focus: OptionalEventCallable = None, - on_blur: OptionalEventCallable = None, - enable_feedback: Optional[bool] = None, # to be deprecated - item_height: OptionalNumber = None, # to be deprecated - alignment: Optional[Alignment] = None, # to be deprecated - hint_content: Optional[Control] = None, # to be deprecated - icon_content: Optional[Control] = None, # to be deprecated - select_icon_size: OptionalNumber = None, # to be deprecated - icon_size: OptionalNumber = None, # to be deprecated - select_icon_enabled_color: Optional[ColorValue] = None, # to be deprecated - icon_enabled_color: Optional[ColorValue] = None, # to be deprecated - select_icon_disabled_color: Optional[ColorValue] = None, # to be deprecated - icon_disabled_color: Optional[ColorValue] = None, # to be deprecated - # - # FormField specific - # - bgcolor: Optional[ColorValue] = None, - error_style: Optional[TextStyle] = None, - error_text: Optional[str] = None, - text_size: OptionalNumber = None, - text_style: Optional[TextStyle] = None, - label: Optional[str] = None, - label_style: Optional[TextStyle] = None, - icon: Optional[IconValueOrControl] = None, # to deprecated - border: Optional[InputBorder] = None, - color: Optional[str] = None, - focused_color: Optional[str] = None, # to be deprecated - focused_bgcolor: Optional[str] = None, # to be deprecated - border_width: OptionalNumber = None, - border_color: Optional[str] = None, - border_radius: Optional[BorderRadiusValue] = None, - focused_border_width: OptionalNumber = None, - focused_border_color: Optional[str] = None, - content_padding: PaddingValue = None, - dense: Optional[bool] = None, - filled: Optional[bool] = None, - fill_color: Optional[str] = None, - hover_color: Optional[str] = None, - hint_text: Optional[str] = None, - hint_style: Optional[TextStyle] = None, - helper_text: Optional[str] = None, - helper_style: Optional[TextStyle] = None, - prefix: Optional[Control] = None, # to be deprecated - prefix_text: Optional[str] = None, # to be deprecated - prefix_style: Optional[TextStyle] = None, # to be deprecated - prefix_icon: Optional[str] = None, # to be deprecated - disabled_hint_content: Optional[Control] = None, # to be deprecated - suffix: Optional[Control] = None, # to be deprecated - suffix_icon: Optional[IconValueOrControl] = None, # to be deprecated - suffix_text: Optional[str] = None, # to be deprecated - suffix_style: Optional[TextStyle] = None, # to be deprecated - counter: Optional[Control] = None, # to be deprecated - counter_text: Optional[str] = None, # to be deprecated - counter_style: Optional[TextStyle] = None, # to be deprecated - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: RotateValue = None, - scale: ScaleValue = None, - offset: OffsetValue = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: AnimationValue = None, - animate_size: AnimationValue = None, - animate_position: AnimationValue = None, - animate_rotation: AnimationValue = None, - animate_scale: AnimationValue = None, - animate_offset: AnimationValue = None, - on_animation_end: OptionalEventCallable = None, - tooltip: Optional[str] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - FormFieldControl.__init__( - self, - text_size=text_size, - text_style=text_style, - label=label, - label_style=label_style, - border=border, - color=color, - border_width=border_width, - border_color=border_color, - border_radius=border_radius, - focused_border_width=focused_border_width, - focused_border_color=focused_border_color, - content_padding=content_padding, - dense=dense, - filled=filled, - fill_color=fill_color, - hover_color=hover_color, - hint_text=hint_text, - hint_style=hint_style, - helper_text=helper_text, - helper_style=helper_style, - error_text=error_text, - error_style=error_style, - prefix_icon=prefix_icon, - ref=ref, - key=key, - width=width, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - visible=visible, - disabled=disabled, - data=data, - ) - - deprecated_properties_list = [ - "select_icon_size", - "select_icon_enabled_color", - "select_icon_disabled_color", - "suffix", - "suffix_icon", - "suffix_style", - "suffix_text", - "icon_content", - "icon_enabled_color", - "icon_disabled_color", - "icon_size", - "icon", - "hint_content", - "prefix_text", - "prefix_style", - "prefix", - "prefix_icon", - "focused_color", - "disabled_hint_content", - "alignment", - "focused_bgcolor", - "item_height", - "enable_feedback", - "options_fill_horizontally", - "padding", - "max_menu_height", - ] - - for item in deprecated_properties_list: - if eval(item) is not None: - warnings.warn( - f"{item} is deprecated since version 0.27.0 " - f"and will be removed in version 0.30.0.", - category=DeprecationWarning, - stacklevel=2, - ) - - self.options = options - self.selected_suffix = selected_suffix - self.input_filter = input_filter - self.enable_filter = enable_filter - self.enable_search = enable_search - self.editable = editable - self.menu_height = menu_height - self.menu_width = menu_width - self.expanded_insets = expanded_insets - self.capitalization = capitalization - self.label_content = label_content - self.leading_icon = leading_icon - self.trailing_icon = trailing_icon - self.selected_trailing_icon = selected_trailing_icon - self.select_icon = select_icon - self.on_change = on_change - self.on_focus = on_focus - self.on_blur = on_blur - self.value = value - self.bgcolor = bgcolor - self.elevation = elevation - self.text_align = text_align - self.autofocus = autofocus - - def _get_control_name(self): - return "dropdown" - - def before_update(self): - super().before_update() - self._set_attr_json("bgcolor", self.__bgcolor, wrap_attr_dict=True) - self._set_attr_json("elevation", self.__elevation, wrap_attr_dict=True) - ##self._set_attr_json("inputFilter", self.__input_filter) - ##self._set_attr_json("expandInsets", self.__expanded_insets) - self.expand_loose = self.expand # to fix a display issue - - def _get_children(self): - children = FormFieldControl._get_children(self) + self.__options - if isinstance(self.__leading_icon, Control): - self.__leading_icon._set_attr_internal("n", "leading_icon") - children.append(self.__leading_icon) - if isinstance(self.__select_icon, Control): - self.__select_icon._set_attr_internal("n", "select_icon") - children.append(self.__select_icon) - if isinstance(self.__trailing_icon, Control): - self.__trailing_icon._set_attr_internal("n", "trailing_icon") - children.append(self.__trailing_icon) - if isinstance(self.__selected_trailing_icon, Control): - self.__selected_trailing_icon._set_attr_internal( - "n", "selected_trailing_icon" - ) - children.append(self.__selected_trailing_icon) - return children - - def __contains__(self, item): - return item in self.__options - - def focus(self): - self._set_attr_json("focus", str(time.time())) - self.update() - - # value - @property - def value(self) -> Optional[str]: - return self._get_attr("value") - - @value.setter - def value(self, value: Optional[str]): - self._set_attr("value", value) - - # options - @property - def options(self) -> Optional[List[Option]]: - return self.__options - - @options.setter - def options(self, value: Optional[List[Option]]): - self.__options = value if value is not None else [] - - # menu_height - @property - def menu_height(self) -> OptionalNumber: - return self._get_attr("menuHeight", data_type="float") - - @menu_height.setter - def menu_height(self, value: OptionalNumber): - self._set_attr("menuHeight", value) - - # menu_width - @property - def menu_width(self) -> OptionalNumber: - return self._get_attr("menuWidth", data_type="float") - - @menu_width.setter - def menu_width(self, value: OptionalNumber): - self._set_attr("menuWidth", value) - - # editable - @property - def editable(self) -> bool: - return self._get_attr("editable", data_type="bool", def_value=False) - - @editable.setter - def editable(self, value: Optional[bool]): - self._set_attr("editable", value) - - # select_icon - @property - def select_icon(self) -> Optional[IconValueOrControl]: - warnings.warn( - f"select_icon is deprecated since version 0.27.0 " - f"and will be removed in version 0.30.0. Use trailing_icon instead.", - category=DeprecationWarning, - stacklevel=2, - ) - return self.__select_icon - - @select_icon.setter - def select_icon(self, value: Optional[IconValueOrControl]): - self.__select_icon = value - - if not isinstance(value, Control): - self._set_enum_attr("selectIcon", value, IconEnums) - - if value is not None: - warnings.warn( - f"select_icon is deprecated since version 0.27.0 " - f"and will be removed in version 0.30.0. Use trailing_icon instead.", - category=DeprecationWarning, - stacklevel=2, - ) - - # leading_icon - @property - def leading_icon(self) -> Optional[IconValueOrControl]: - return self.__leading_icon - - @leading_icon.setter - def leading_icon(self, value: Optional[IconValueOrControl]): - self.__leading_icon = value - if not isinstance(value, Control): - self._set_enum_attr("leadingIcon", value, IconEnums) - - # trailing_icon - @property - def trailing_icon(self) -> Optional[IconValueOrControl]: - return self.__trailing_icon - - @trailing_icon.setter - def trailing_icon(self, value: Optional[IconValueOrControl]): - self.__trailing_icon = value - if not isinstance(value, Control): - self._set_enum_attr("trailingIcon", value, IconEnums) - - # selected_trailing_icon - @property - def selected_trailing_icon(self) -> Optional[IconValueOrControl]: - return self.__selected_trailing_icon - - @selected_trailing_icon.setter - def selected_trailing_icon(self, value: Optional[IconValueOrControl]): - self.__selected_trailing_icon = value - if not isinstance(value, Control): - self._set_enum_attr("selectedTrailingIcon", value, IconEnums) - - # bgcolor - @property - def bgcolor(self) -> ControlStateValue[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: ControlStateValue[ColorValue]): - self.__bgcolor = value - - # text_align - @property - def text_align(self) -> Optional[TextAlign]: - return self.__text_align - - @text_align.setter - def text_align(self, value: Optional[TextAlign]): - self.__text_align = value - self._set_enum_attr("textAlign", value, TextAlign) - - # elevation - @property - def elevation(self) -> Union[OptionalNumber, Dict[ControlState, Number]]: - return self.__elevation - - @elevation.setter - def elevation(self, value: Union[OptionalNumber, Dict[ControlState, Number]]): - self.__elevation = value - - # enable_filter - @property - def enable_filter(self) -> bool: - return self._get_attr("enableFilter", data_type="bool", def_value=False) - - @enable_filter.setter - def enable_filter(self, value: Optional[bool]): - self._set_attr("enableFilter", value) - - # enable_search - @property - def enable_search(self) -> bool: - return self._get_attr("enableSearch", data_type="bool", def_value=True) - - @enable_search.setter - def enable_search(self, value: Optional[bool]): - self._set_attr("enableSearch", value) - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) diff --git a/sdk/python/packages/flet/src/flet/core/dropdownm2.py b/sdk/python/packages/flet/src/flet/core/dropdownm2.py deleted file mode 100644 index db4d0ca48..000000000 --- a/sdk/python/packages/flet/src/flet/core/dropdownm2.py +++ /dev/null @@ -1,594 +0,0 @@ -import time -import warnings -from typing import Any, List, Optional, Union - -from flet.core.alignment import Alignment -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.box import BoxConstraints -from flet.core.control import Control, OptionalNumber -from flet.core.form_field_control import FormFieldControl, InputBorder -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - BorderRadiusValue, - ColorEnums, - ColorValue, - DurationValue, - IconEnums, - IconValueOrControl, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class Option(Control): - def __init__( - self, - key: Optional[str] = None, - text: Optional[str] = None, - content: Optional[Control] = None, - alignment: Optional[Alignment] = None, - text_style: Optional[TextStyle] = None, - on_click: OptionalControlEventCallable = None, - # - # Control - # - ref=None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - Control.__init__(self, ref=ref, disabled=disabled, visible=visible, data=data) - self.key = key - self.text = text - self.content = content - self.on_click = on_click - self.alignment = alignment - self.text_style = text_style - - def _get_control_name(self): - return "dropdownoption" - - def _get_children(self): - children = [] - if self.__content is not None: - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - return children - - def before_update(self): - super().before_update() - assert ( - self.key is not None or self.text is not None - ), "key or text must be specified" - self._set_attr_json("alignment", self.__alignment) - if isinstance(self.__text_style, TextStyle): - self._set_attr_json("textStyle", self.__text_style) - - # key - @property - def key(self) -> Optional[str]: - return self._get_attr("key") - - @key.setter - def key(self, value: Optional[str]): - self._set_attr("key", value) - - # text - @property - def text(self) -> Optional[str]: - return self._get_attr("text") - - @text.setter - def text(self, value: Optional[str]): - self._set_attr("text", value) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value - - # text_style - @property - def text_style(self) -> Optional[TextStyle]: - return self.__text_style - - @text_style.setter - def text_style(self, value: Optional[TextStyle]): - self.__text_style = value - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) - - -class DropdownM2(FormFieldControl): - """ - A dropdown lets the user select from a number of items. The dropdown shows the currently selected item as well as an arrow that opens a menu for selecting another item. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - def button_clicked(e): - t.value = f"Dropdown value is: {dd.value}" - page.update() - - t = ft.Text() - b = ft.ElevatedButton(text="Submit", on_click=button_clicked) - dd = ft.DropdownM2( - width=200, - options=[ - ft.dropdown.Option("Red"), - ft.dropdown.Option("Green"), - ft.dropdown.Option("Blue"), - ], - ) - page.add(dd, b, t) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/dropdown - """ - - def __init__( - self, - value: Optional[str] = None, - options: Optional[List[Option]] = None, - alignment: Optional[Alignment] = None, - autofocus: Optional[bool] = None, - hint_content: Optional[Control] = None, - select_icon: Optional[IconValueOrControl] = None, - elevation: OptionalNumber = None, - item_height: OptionalNumber = None, - max_menu_height: OptionalNumber = None, - select_icon_size: OptionalNumber = None, - enable_feedback: Optional[bool] = None, - padding: Optional[PaddingValue] = None, - select_icon_enabled_color: Optional[ColorValue] = None, - select_icon_disabled_color: Optional[ColorValue] = None, - options_fill_horizontally: Optional[bool] = None, - disabled_hint_content: Optional[Control] = None, - on_change: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - on_click: OptionalControlEventCallable = None, - # - # FormField specific - # - text_size: OptionalNumber = None, - text_style: Optional[TextStyle] = None, - label: Optional[Union[str, Control]] = None, - label_style: Optional[TextStyle] = None, - icon: Optional[IconValueOrControl] = None, - border: Optional[InputBorder] = None, - color: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, - border_radius: Optional[BorderRadiusValue] = None, - border_width: OptionalNumber = None, - border_color: Optional[ColorValue] = None, - focused_color: Optional[ColorValue] = None, - focused_bgcolor: Optional[ColorValue] = None, - focused_border_width: OptionalNumber = None, - focused_border_color: Optional[ColorValue] = None, - content_padding: Optional[PaddingValue] = None, - dense: Optional[bool] = None, - filled: Optional[bool] = None, - fill_color: Optional[ColorValue] = None, - hint_text: Optional[str] = None, - hint_style: Optional[TextStyle] = None, - helper: Optional[Control] = None, - helper_text: Optional[str] = None, - helper_style: Optional[TextStyle] = None, - counter: Optional[Control] = None, - counter_text: Optional[str] = None, - counter_style: Optional[TextStyle] = None, - error: Optional[Control] = None, - error_text: Optional[str] = None, - error_style: Optional[TextStyle] = None, - prefix: Optional[Control] = None, - prefix_icon: Optional[IconValueOrControl] = None, - prefix_text: Optional[str] = None, - prefix_style: Optional[TextStyle] = None, - suffix: Optional[Control] = None, - suffix_icon: Optional[IconValueOrControl] = None, - suffix_text: Optional[str] = None, - suffix_style: Optional[TextStyle] = None, - focus_color: Optional[ColorValue] = None, - align_label_with_hint: Optional[bool] = None, - hint_fade_duration: Optional[DurationValue] = None, - hint_max_lines: Optional[int] = None, - helper_max_lines: Optional[int] = None, - error_max_lines: Optional[int] = None, - prefix_icon_size_constraints: Optional[BoxConstraints] = None, - suffix_icon_size_constraints: Optional[BoxConstraints] = None, - size_constraints: Optional[BoxConstraints] = None, - collapsed: Optional[bool] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - FormFieldControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - # - # FormField specific - # - text_size=text_size, - text_style=text_style, - label=label, - label_style=label_style, - icon=icon, - border=border, - color=color, - bgcolor=bgcolor, - border_radius=border_radius, - border_width=border_width, - border_color=border_color, - focused_color=focused_color, - focused_bgcolor=focused_bgcolor, - focused_border_width=focused_border_width, - focused_border_color=focused_border_color, - content_padding=content_padding, - dense=dense, - filled=filled, - fill_color=fill_color, - hint_text=hint_text, - hint_style=hint_style, - helper_text=helper_text, - helper=helper, - helper_style=helper_style, - counter=counter, - counter_text=counter_text, - counter_style=counter_style, - error=error, - error_text=error_text, - error_style=error_style, - prefix=prefix, - prefix_icon=prefix_icon, - prefix_text=prefix_text, - prefix_style=prefix_style, - suffix=suffix, - suffix_icon=suffix_icon, - suffix_text=suffix_text, - suffix_style=suffix_style, - focus_color=focus_color, - align_label_with_hint=align_label_with_hint, - hint_fade_duration=hint_fade_duration, - hint_max_lines=hint_max_lines, - helper_max_lines=helper_max_lines, - error_max_lines=error_max_lines, - prefix_icon_size_constraints=prefix_icon_size_constraints, - suffix_icon_size_constraints=suffix_icon_size_constraints, - size_constraints=size_constraints, - collapsed=collapsed, - ) - - self.value = value - self.autofocus = autofocus - self.options = options - self.alignment = alignment - self.elevation = elevation - self.hint_content = hint_content - self.disabled_hint_content = disabled_hint_content - self.select_icon = select_icon - self.padding = padding - self.enable_feedback = enable_feedback - self.on_focus = on_focus - self.on_blur = on_blur - self.on_change = on_change - self.item_height = item_height - self.max_menu_height = max_menu_height - self.select_icon_size = select_icon_size - self.select_icon_enabled_color = select_icon_enabled_color - self.select_icon_disabled_color = select_icon_disabled_color - - self.on_click = on_click - self.options_fill_horizontally = options_fill_horizontally - - warnings.warn( - f"DropdownM2 control is deprecated since version 0.27.0 " - f"and will be removed in version 0.30.0. User Dropdown control instead", - category=DeprecationWarning, - stacklevel=2, - ) - - def _get_control_name(self): - return "dropdownm2" - - def before_update(self): - super().before_update() - self._set_attr_json("padding", self.__padding) - self._set_attr_json("alignment", self.__alignment) - if ( - ( - self.bgcolor is not None - or self.fill_color is not None - or self.focused_bgcolor is not None - ) - ) and self.filled is None: - self.filled = True # required to display any of the above colors - - def _get_children(self): - children = FormFieldControl._get_children(self) + self.__options - if isinstance(self.__hint_content, Control): - self.__hint_content._set_attr_internal("n", "hint") - children.append(self.__hint_content) - if isinstance(self.__select_icon, Control): - self.__select_icon._set_attr_internal("n", "selectIcon") - children.append(self.__select_icon) - if isinstance(self.__disabled_hint_content, Control): - self.__disabled_hint_content._set_attr_internal("n", "disabled_hint") - children.append(self.__disabled_hint_content) - return children - - def __contains__(self, item): - return item in self.__options - - def focus(self): - self._set_attr_json("focus", str(time.time())) - self.update() - - # options - @property - def options(self): - return self.__options - - @options.setter - def options(self, value): - self.__options = value if value is not None else [] - - # select_icon - @property - def select_icon(self) -> Optional[IconValueOrControl]: - return self.__select_icon - - @select_icon.setter - def select_icon(self, value: Optional[IconValueOrControl]): - self.__select_icon = value - if not isinstance(value, Control): - self._set_enum_attr("selectIcon", value, IconEnums) - - # hint_content - @property - def hint_content(self) -> Optional[Control]: - return self.__hint_content - - @hint_content.setter - def hint_content(self, value: Optional[Control]): - self.__hint_content = value - - # disabled_hint_content - @property - def disabled_hint_content(self) -> Optional[Control]: - return self.__disabled_hint_content - - @disabled_hint_content.setter - def disabled_hint_content(self, value: Optional[Control]): - self.__disabled_hint_content = value - - # value - @property - def value(self) -> Optional[str]: - return self._get_attr("value") - - @value.setter - def value(self, value: Optional[str]): - self._set_attr("value", value) - - # select_icon_enabled_color - @property - def select_icon_enabled_color(self) -> Optional[ColorValue]: - return self.__select_icon_enabled_color - - @select_icon_enabled_color.setter - def select_icon_enabled_color(self, value: Optional[ColorValue]): - self.__select_icon_enabled_color = value - self._set_enum_attr("selectIconEnabledColor", value, ColorEnums) - - # select_icon_disabled_color - @property - def select_icon_disabled_color(self) -> Optional[ColorValue]: - return self.__select_icon_disabled_color - - @select_icon_disabled_color.setter - def select_icon_disabled_color(self, value: Optional[ColorValue]): - self.__select_icon_disabled_color = value - self._set_enum_attr("selectIconDisabledColor", value, ColorEnums) - - # item_height - @property - def item_height(self) -> OptionalNumber: - return self._get_attr("itemHeight", data_type="float") - - @item_height.setter - def item_height(self, value: OptionalNumber): - assert ( - value is None or value >= 48.0 - ), "item_height must be greater than or equal to 48.0" - self._set_attr("itemHeight", value) - - # max_menu_height - @property - def max_menu_height(self) -> OptionalNumber: - return self._get_attr("maxMenuHeight", data_type="float") - - @max_menu_height.setter - def max_menu_height(self, value: OptionalNumber): - self._set_attr("maxMenuHeight", value) - - # select_icon_size - @property - def select_icon_size(self) -> float: - return self._get_attr("selectIconSize", data_type="float", def_value=24.0) - - @select_icon_size.setter - def select_icon_size(self, value: OptionalNumber): - self._set_attr("selectIconSize", value) - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # options_fill_horizontally - @property - def options_fill_horizontally(self) -> bool: - return self._get_attr( - "optionsFillHorizontally", data_type="bool", def_value=False - ) - - @options_fill_horizontally.setter - def options_fill_horizontally(self, value: Optional[bool]): - self._set_attr("optionsFillHorizontally", value) - - # enable_feedback - @property - def enable_feedback(self) -> bool: - return self._get_attr("enableFeedback", data_type="bool", def_value=True) - - @enable_feedback.setter - def enable_feedback(self, value: Optional[bool]): - self._set_attr("enableFeedback", value) - - # elevation - @property - def elevation(self) -> float: - return self._get_attr("elevation", data_type="float", def_value=8.0) - - @elevation.setter - def elevation(self, value: OptionalNumber): - self._set_attr("elevation", value) - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) diff --git a/sdk/python/packages/flet/src/flet/core/elevated_button.py b/sdk/python/packages/flet/src/flet/core/elevated_button.py deleted file mode 100644 index a05f2792f..000000000 --- a/sdk/python/packages/flet/src/flet/core/elevated_button.py +++ /dev/null @@ -1,344 +0,0 @@ -import time -from typing import Any, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.buttons import ButtonStyle -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ClipBehavior, - ColorEnums, - ColorValue, - IconEnums, - IconValue, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, - UrlTarget, -) - - -class ElevatedButton(ConstrainedControl, AdaptiveControl): - """ - Elevated buttons are essentially filled tonal buttons with a shadow. To prevent shadow creep, only use them when absolutely necessary, such as when the button requires visual separation from a patterned background. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.title = "Basic elevated buttons" - page.add( - ft.ElevatedButton(text="Elevated button"), - ft.ElevatedButton("Disabled button", disabled=True), - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/elevatedbutton - """ - - def __init__( - self, - text: Optional[str] = None, - icon: Optional[IconValue] = None, - icon_color: Optional[ColorValue] = None, - color: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, - content: Optional[Control] = None, - elevation: OptionalNumber = None, - style: Optional[ButtonStyle] = None, - autofocus: Optional[bool] = None, - clip_behavior: Optional[ClipBehavior] = None, - url: Optional[str] = None, - url_target: Optional[UrlTarget] = None, - on_click: OptionalControlEventCallable = None, - on_long_press: OptionalControlEventCallable = None, - on_hover: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl and AdaptiveControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.text = text - self.color = color - self.bgcolor = bgcolor - self.elevation = elevation - self.style = style - self.icon = icon - self.icon_color = icon_color - self.content = content - self.autofocus = autofocus - self.url = url - self.url_target = url_target - self.on_click = on_click - self.on_long_press = on_long_press - self.on_hover = on_hover - self.on_focus = on_focus - self.on_blur = on_blur - self.clip_behavior = clip_behavior - - def _get_control_name(self): - return "elevatedbutton" - - def before_update(self): - super().before_update() - assert ( - self.text or self.icon or (self.__content and self.__content.visible) - ), "at minimum, text, icon or a visible content must be provided" - style = self.__style or ButtonStyle() - if self.__color is not None: - style.color = self.__color - if self.__bgcolor is not None: - style.bgcolor = self.__bgcolor - if self.__elevation is not None: - style.elevation = self.__elevation - - style.side = self._wrap_attr_dict(style.side) - style.shape = self._wrap_attr_dict(style.shape) - style.padding = self._wrap_attr_dict(style.padding) - style.text_style = self._wrap_attr_dict(style.text_style) - self._set_attr_json("style", style) - - def _get_children(self): - if self.__content is None: - return [] - self.__content._set_attr_internal("n", "content") - return [self.__content] - - def focus(self): - self._set_attr_json("focus", str(time.time())) - self.update() - - # text - @property - def text(self) -> Optional[str]: - return self._get_attr("text") - - @text.setter - def text(self, value: Optional[str]): - self._set_attr("text", value) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgColor", value, ColorEnums) - - # elevation - @property - def elevation(self) -> OptionalNumber: - return self.__elevation - - @elevation.setter - def elevation(self, value: OptionalNumber): - self.__elevation = value - - # style - @property - def style(self) -> Optional[ButtonStyle]: - return self.__style - - @style.setter - def style(self, value: Optional[ButtonStyle]): - self.__style = value - - # icon - @property - def icon(self) -> Optional[IconValue]: - return self.__icon - - @icon.setter - def icon(self, value: Optional[IconValue]): - self.__icon = value - self._set_enum_attr("icon", value, IconEnums) - - # icon_color - @property - def icon_color(self) -> Optional[ColorValue]: - return self.__icon_color - - @icon_color.setter - def icon_color(self, value: Optional[ColorValue]): - self.__icon_color = value - self._set_enum_attr("iconColor", value, ColorEnums) - - # url - @property - def url(self) -> Optional[str]: - return self._get_attr("url") - - @url.setter - def url(self, value: Optional[str]): - self._set_attr("url", value) - - # url_target - @property - def url_target(self) -> Optional[UrlTarget]: - return self.__url_target - - @url_target.setter - def url_target(self, value: Optional[UrlTarget]): - self.__url_target = value - self._set_enum_attr("urlTarget", value, UrlTarget) - - # on_click - @property - def on_click(self): - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler): - self._add_event_handler("click", handler) - - # on_long_press - @property - def on_long_press(self): - return self._get_event_handler("long_press") - - @on_long_press.setter - def on_long_press(self, handler): - self._add_event_handler("long_press", handler) - self._set_attr("onLongPress", True if handler is not None else None) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # on_hover - @property - def on_hover(self) -> OptionalControlEventCallable: - return self._get_event_handler("hover") - - @on_hover.setter - def on_hover(self, handler: OptionalControlEventCallable): - self._add_event_handler("hover", handler) - self._set_attr("onHover", True if handler is not None else None) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) diff --git a/sdk/python/packages/flet/src/flet/core/embed_json_encoder.py b/sdk/python/packages/flet/src/flet/core/embed_json_encoder.py deleted file mode 100644 index fdf0cd6e0..000000000 --- a/sdk/python/packages/flet/src/flet/core/embed_json_encoder.py +++ /dev/null @@ -1,81 +0,0 @@ -import enum -import json -from typing import Dict - -from flet.core.border import Border, BorderSide -from flet.core.border_radius import BorderRadius -from flet.core.box import BoxConstraints -from flet.core.margin import Margin -from flet.core.padding import Padding - - -class EmbedJsonEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, BorderSide): - obj_as_dict = { - "w": obj.width, - "c": obj.color, - "sa": obj.stroke_align, - } - elif isinstance(obj, Border): - obj_as_dict = { - "l": obj.left, - "t": obj.top, - "r": obj.right, - "b": obj.bottom, - } - elif isinstance(obj, BorderRadius): - obj_as_dict = { - "bl": obj.bottom_left, - "br": obj.bottom_right, - "tl": obj.top_left, - "tr": obj.top_right, - } - elif isinstance(obj, (Margin, Padding)): - obj_as_dict = { - "l": obj.left, - "t": obj.top, - "r": obj.right, - "b": obj.bottom, - } - elif isinstance(obj, BoxConstraints): - obj_as_dict = { - "min_width": obj.min_width, - "max_width": obj.max_width, - "min_height": obj.min_height, - "max_height": obj.max_height, - } - else: - obj_as_dict = self._convert_enums(obj.__dict__) - - # Convert inf to string "inf" to avoid JSON serialization error - for key, value in obj_as_dict.items(): - if value == float("inf"): - obj_as_dict[key] = "inf" - - return obj_as_dict - - def encode(self, o): - return super().encode(self._convert_enums(o)) - - def _convert_enums(self, obj): - if isinstance(obj, Dict): - return dict( - map( - lambda item: ( - self._convert_enums( - item[0] - if not isinstance(item[0], enum.Enum) - else item[0].value - ), - self._convert_enums( - item[1] - if not isinstance(item[1], enum.Enum) - else item[1].value - ), - ), - filter(lambda item: item[1] is not None, obj.items()), - ) - ) - else: - return obj diff --git a/sdk/python/packages/flet/src/flet/core/event.py b/sdk/python/packages/flet/src/flet/core/event.py deleted file mode 100644 index 0c7235b75..000000000 --- a/sdk/python/packages/flet/src/flet/core/event.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Optional - - -class Event: - def __init__(self, target: str, name: str, data: Optional[str]): - self.target = target - self.name = name - self.data = data - - def __repr__(self): - attrs = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items()) - return f"{self.__class__.__name__}({attrs})" - - def __str__(self): - attrs = ", ".join( - f"{k}={v!r}" - for k, v in self.__dict__.items() - if k not in ["control", "page", "target", "data"] # ignore these keys - ) - return f"{self.__class__.__name__}({attrs}, data={self.data!r})" # reinsert data as last arg diff --git a/sdk/python/packages/flet/src/flet/core/event_handler.py b/sdk/python/packages/flet/src/flet/core/event_handler.py deleted file mode 100644 index 3f5c9a143..000000000 --- a/sdk/python/packages/flet/src/flet/core/event_handler.py +++ /dev/null @@ -1,31 +0,0 @@ -import asyncio - -from flet.core.control_event import ControlEvent -from flet.core.types import OptionalControlEventCallable - - -class EventHandler: - def __init__(self, result_converter=None) -> None: - self.__result_converter = result_converter - self.handler: OptionalControlEventCallable = None - - def get_handler(self): - async def fn(e: ControlEvent): - if self.handler is not None: - ce = e - if self.__result_converter is not None: - ce = self.__result_converter(e) - if ce is not None: - ce.target = e.target - ce.name = e.name - ce.data = e.data - ce.control = e.control - ce.page = e.page - - if ce is not None: - if asyncio.iscoroutinefunction(self.handler): - await self.handler(ce) - else: - e.page.run_thread(self.handler, ce) - - return fn diff --git a/sdk/python/packages/flet/src/flet/core/expansion_panel.py b/sdk/python/packages/flet/src/flet/core/expansion_panel.py deleted file mode 100644 index 37cc9a3d0..000000000 --- a/sdk/python/packages/flet/src/flet/core/expansion_panel.py +++ /dev/null @@ -1,361 +0,0 @@ -from typing import Any, Optional, Sequence, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class ExpansionPanel(ConstrainedControl, AdaptiveControl): - """ - A material expansion panel. It can either be expanded or collapsed. Its body is only visible when it is expanded. - - ----- - - Online docs: https://flet.dev/docs/controls/expansionpanel - """ - - def __init__( - self, - header: Optional[Control] = None, - content: Optional[Control] = None, - bgcolor: Optional[ColorValue] = None, - expanded: Optional[bool] = None, - can_tap_header: Optional[bool] = None, - splash_color: Optional[ColorValue] = None, - highlight_color: Optional[ColorValue] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - # - # Adaptive - # - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.header = header - self.content = content - self.bgcolor = bgcolor - self.expanded = expanded - self.can_tap_header = can_tap_header - self.splash_color = splash_color - self.highlight_color = highlight_color - - def _get_control_name(self): - return "expansionpanel" - - def before_update(self): - super().before_update() - - def _get_children(self): - children = [] - if self.__header: - self.__header._set_attr_internal("n", "header") - children.append(self.__header) - if self.__content: - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - return children - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgColor", value, ColorEnums) - - # splash_color - @property - def splash_color(self) -> Optional[ColorValue]: - - return self.__splash_color - - @splash_color.setter - def splash_color(self, value: Optional[ColorValue]): - self.__splash_color = value - self._set_enum_attr("splashColor", value, ColorEnums) - - # highlight_color - @property - def highlight_color(self) -> Optional[ColorValue]: - return self.__highlight_color - - @highlight_color.setter - def highlight_color(self, value: Optional[ColorValue]): - self.__highlight_color = value - self._set_enum_attr("highlightColor", value, ColorEnums) - - # expanded - @property - def expanded(self) -> bool: - return self._get_attr("expanded", data_type="bool", def_value=False) - - @expanded.setter - def expanded(self, value: Optional[bool]): - self._set_attr("expanded", value) - - # can_tap_header - @property - def can_tap_header(self) -> bool: - return self._get_attr("canTapHeader", data_type="bool", def_value=False) - - @can_tap_header.setter - def can_tap_header(self, value: Optional[bool]): - self._set_attr("canTapHeader", value) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # header - @property - def header(self) -> Optional[Control]: - return self.__header - - @header.setter - def header(self, value: Optional[Control]): - self.__header = value - - -class ExpansionPanelList(ConstrainedControl): - """ - A material expansion panel list that lays out its children and animates expansions. - - ----- - - Online docs: https://flet.dev/docs/controls/expansionpanellist - """ - - def __init__( - self, - controls: Optional[Sequence[ExpansionPanel]] = None, - divider_color: Optional[ColorValue] = None, - elevation: OptionalNumber = None, - expanded_header_padding: Optional[PaddingValue] = None, - expand_icon_color: Optional[ColorValue] = None, - spacing: OptionalNumber = None, - on_change: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - ) - - self.controls = controls - self.divider_color = divider_color - self.expanded_icon_color = expand_icon_color - self.expanded_header_padding = expanded_header_padding - self.elevation = elevation - self.spacing = spacing - self.on_change = on_change - - def _get_control_name(self): - return "expansionpanellist" - - def before_update(self): - super().before_update() - self._set_attr_json("expandedHeaderPadding", self.__expanded_header_padding) - - def _get_children(self): - children = [] - for c in self.__controls: - c._set_attr_internal("n", "expansionpanel") - children.append(c) - return children - - # divider_color - @property - def divider_color(self) -> Optional[ColorValue]: - return self.__divider_color - - @divider_color.setter - def divider_color(self, value: Optional[ColorValue]): - self.__divider_color = value - self._set_enum_attr("dividerColor", value, ColorEnums) - - # expanded_icon_color - @property - def expanded_icon_color(self) -> Optional[ColorValue]: - return self.__expanded_icon_color - - @expanded_icon_color.setter - def expanded_icon_color(self, value: Optional[ColorValue]): - self.__expanded_icon_color = value - self._set_enum_attr("expandedIconColor", value, ColorEnums) - - # expanded_header_padding - @property - def expanded_header_padding(self) -> Optional[PaddingValue]: - return self.__expanded_header_padding - - @expanded_header_padding.setter - def expanded_header_padding(self, value: Optional[PaddingValue]): - self.__expanded_header_padding = value - - # elevation - @property - def elevation(self) -> OptionalNumber: - return self._get_attr("elevation", data_type="float") - - @elevation.setter - def elevation(self, value: OptionalNumber): - assert value is None or value >= 0, "elevation cannot be negative" - self._set_attr("elevation", value) - - # spacing - @property - def spacing(self) -> OptionalNumber: - return self._get_attr("spacing", data_type="float") - - @spacing.setter - def spacing(self, value: OptionalNumber): - self._set_attr("spacing", value) - - # controls - @property - def controls(self): - return self.__controls - - @controls.setter - def controls(self, value: Optional[Sequence[ExpansionPanel]]): - self.__controls = list(value) if value is not None else [] - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - self._set_attr("onChange", True if handler is not None else None) diff --git a/sdk/python/packages/flet/src/flet/core/expansion_tile.py b/sdk/python/packages/flet/src/flet/core/expansion_tile.py deleted file mode 100644 index 99be19d01..000000000 --- a/sdk/python/packages/flet/src/flet/core/expansion_tile.py +++ /dev/null @@ -1,451 +0,0 @@ -from enum import Enum -from typing import Any, Optional, Sequence, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.alignment import Alignment -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.buttons import OutlinedBorder -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ClipBehavior, - ColorEnums, - ColorValue, - CrossAxisAlignment, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, - VisualDensity, -) - - -class TileAffinity(Enum): - LEADING = "leading" - TRAILING = "trailing" - PLATFORM = "platform" - - -class ExpansionTile(ConstrainedControl, AdaptiveControl): - """ - A single-line ListTile with an expansion arrow icon that expands or collapses the tile to reveal or hide its controls. - - ----- - - Online docs: https://flet.dev/docs/controls/expansiontile - """ - - def __init__( - self, - title: Control, - controls: Optional[Sequence[Control]] = None, - subtitle: Optional[Control] = None, - leading: Optional[Control] = None, - trailing: Optional[Control] = None, - controls_padding: Optional[PaddingValue] = None, - tile_padding: Optional[PaddingValue] = None, - affinity: Optional[TileAffinity] = None, - expanded_alignment: Optional[Alignment] = None, - expanded_cross_axis_alignment: CrossAxisAlignment = CrossAxisAlignment.CENTER, - clip_behavior: Optional[ClipBehavior] = None, - initially_expanded: Optional[bool] = None, - maintain_state: Optional[bool] = None, - text_color: Optional[ColorValue] = None, - icon_color: Optional[ColorValue] = None, - shape: Optional[OutlinedBorder] = None, - bgcolor: Optional[ColorValue] = None, - collapsed_bgcolor: Optional[ColorValue] = None, - collapsed_icon_color: Optional[ColorValue] = None, - collapsed_text_color: Optional[ColorValue] = None, - collapsed_shape: Optional[OutlinedBorder] = None, - dense: Optional[bool] = None, - enable_feedback: Optional[bool] = None, - show_trailing_icon: Optional[bool] = None, - min_tile_height: OptionalNumber = None, - visual_density: Optional[VisualDensity] = None, - on_change: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - # - # AdaptiveControl - # - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.controls = controls - self.controls_padding = controls_padding - self.expanded_alignment = expanded_alignment - self.expanded_cross_axis_alignment = expanded_cross_axis_alignment - self.tile_padding = tile_padding - self.leading = leading - self.title = title - self.subtitle = subtitle - self.trailing = trailing - self.affinity = affinity - self.clip_behavior = clip_behavior - self.maintain_state = maintain_state - self.initially_expanded = initially_expanded - self.shape = shape - self.text_color = text_color - self.icon_color = icon_color - self.bgcolor = bgcolor - self.collapsed_bgcolor = collapsed_bgcolor - self.collapsed_icon_color = collapsed_icon_color - self.collapsed_text_color = collapsed_text_color - self.collapsed_shape = collapsed_shape - self.on_change = on_change - self.dense = dense - self.enable_feedback = enable_feedback - self.visual_density = visual_density - self.show_trailing_icon = show_trailing_icon - self.min_tile_height = min_tile_height - - def _get_control_name(self): - return "expansiontile" - - def before_update(self): - super().before_update() - assert self.__title.visible, "title must be visible" - self._set_attr_json("expandedAlignment", self.__expanded_alignment) - self._set_attr_json("controlsPadding", self.__controls_padding) - self._set_attr_json("tilePadding", self.__tile_padding) - self._set_attr_json("shape", self.__shape) - self._set_attr_json("collapsedShape", self.__collapsed_shape) - - def _get_children(self): - self.__title._set_attr_internal("n", "title") - children = [self.__title] - for c in self.__controls: - c._set_attr_internal("n", "controls") - children.append(c) - if self.__leading: - self.__leading._set_attr_internal("n", "leading") - children.append(self.__leading) - if self.__subtitle: - self.__subtitle._set_attr_internal("n", "subtitle") - children.append(self.__subtitle) - if self.__trailing: - self.__trailing._set_attr_internal("n", "trailing") - children.append(self.__trailing) - return children - - # controls - @property - def controls(self): - return self.__controls - - @controls.setter - def controls(self, value: Optional[Sequence[Control]]): - self.__controls = list(value) if value is not None else [] - - # controls_padding - @property - def controls_padding(self) -> Optional[PaddingValue]: - return self.__controls_padding - - @controls_padding.setter - def controls_padding(self, value: Optional[PaddingValue]): - self.__controls_padding = value - - # tile_padding - @property - def tile_padding(self) -> Optional[PaddingValue]: - return self.__tile_padding - - @tile_padding.setter - def tile_padding(self, value: Optional[PaddingValue]): - self.__tile_padding = value - - # expanded_alignment - @property - def expanded_alignment(self) -> Optional[Alignment]: - return self.__expanded_alignment - - @expanded_alignment.setter - def expanded_alignment(self, value: Optional[Alignment]): - self.__expanded_alignment = value - - # expanded_cross_axis_alignment - @property - def expanded_cross_axis_alignment(self) -> Optional[CrossAxisAlignment]: - return self.__expanded_cross_axis_alignment - - @expanded_cross_axis_alignment.setter - def expanded_cross_axis_alignment(self, value: Optional[CrossAxisAlignment]): - self.__expanded_cross_axis_alignment = value - self._set_enum_attr("crossAxisAlignment", value, CrossAxisAlignment) - - # affinity - @property - def affinity(self) -> Optional[TileAffinity]: - return self.__affinity - - @affinity.setter - def affinity(self, value: Optional[TileAffinity]): - self.__affinity = value - self._set_enum_attr("affinity", value, TileAffinity) - - # leading - @property - def leading(self) -> Optional[Control]: - return self.__leading - - @leading.setter - def leading(self, value: Optional[Control]): - self.__leading = value - - # title - @property - def title(self) -> Optional[Control]: - return self.__title - - @title.setter - def title(self, value: Optional[Control]): - self.__title = value - - # subtitle - @property - def subtitle(self) -> Optional[Control]: - return self.__subtitle - - @subtitle.setter - def subtitle(self, value: Optional[Control]): - self.__subtitle = value - - # trailing - @property - def trailing(self) -> Optional[Control]: - return self.__trailing - - @trailing.setter - def trailing(self, value: Optional[Control]): - self.__trailing = value - - # dense - @property - def dense(self) -> bool: - return self._get_attr("dense", data_type="bool") - - @dense.setter - def dense(self, value: Optional[bool]): - self._set_attr("dense", value) - - # enable_feedback - @property - def enable_feedback(self) -> bool: - return self._get_attr("enableFeedback", data_type="bool", def_value=True) - - @enable_feedback.setter - def enable_feedback(self, value: Optional[bool]): - self._set_attr("enableFeedback", value) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # visual_density - @property - def visual_density(self) -> Optional[VisualDensity]: - return self.__visual_density - - @visual_density.setter - def visual_density(self, value: Optional[VisualDensity]): - self.__visual_density = value - self._set_enum_attr("visualDensity", value, VisualDensity) - - # maintain_state - @property - def maintain_state(self) -> bool: - return self._get_attr("maintainState", data_type="bool", def_value=False) - - @maintain_state.setter - def maintain_state(self, value: Optional[bool]): - self._set_attr("maintainState", value) - - # initially_expanded - @property - def initially_expanded(self) -> bool: - return self._get_attr("initiallyExpanded", data_type="bool", def_value=False) - - @initially_expanded.setter - def initially_expanded(self, value: Optional[bool]): - self._set_attr("initiallyExpanded", value) - - # shape - @property - def shape(self) -> Optional[OutlinedBorder]: - return self.__shape - - @shape.setter - def shape(self, value: Optional[OutlinedBorder]): - self.__shape = value - - # text_color - @property - def text_color(self) -> Optional[ColorValue]: - return self.__text_color - - @text_color.setter - def text_color(self, value: Optional[ColorValue]): - self.__text_color = value - self._set_enum_attr("textColor", value, ColorEnums) - - # icon_color - @property - def icon_color(self) -> Optional[ColorValue]: - return self.__icon_color - - @icon_color.setter - def icon_color(self, value: Optional[ColorValue]): - self.__icon_color = value - self._set_enum_attr("iconColor", value, ColorEnums) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgColor", value, ColorEnums) - - # collapsed_bgcolor - @property - def collapsed_bgcolor(self) -> Optional[str]: - return self.__collapsed_bgcolor - - @collapsed_bgcolor.setter - def collapsed_bgcolor(self, value: Optional[str]): - self.__collapsed_bgcolor = value - self._set_enum_attr("collapsedBgColor", value, ColorEnums) - - # collapsed_icon_color - @property - def collapsed_icon_color(self) -> Optional[ColorValue]: - return self.__collapsed_icon_color - - @collapsed_icon_color.setter - def collapsed_icon_color(self, value: Optional[ColorValue]): - self.__collapsed_icon_color = value - self._set_enum_attr("collapsedIconColor", value, ColorEnums) - - # collapsed_text_color - @property - def collapsed_text_color(self) -> Optional[ColorValue]: - return self.__collapsed_text_color - - @collapsed_text_color.setter - def collapsed_text_color(self, value: Optional[ColorValue]): - self.__collapsed_text_color = value - self._set_enum_attr("collapsedTextColor", value, ColorEnums) - - # collapsed_shape - @property - def collapsed_shape(self) -> Optional[OutlinedBorder]: - return self.__collapsed_shape - - @collapsed_shape.setter - def collapsed_shape(self, value: Optional[OutlinedBorder]): - self.__collapsed_shape = value - - # show_trailing_icon - @property - def show_trailing_icon(self) -> bool: - return self._get_attr("showTrailingIcon", data_type="bool", def_value=True) - - @show_trailing_icon.setter - def show_trailing_icon(self, value: Optional[bool]): - self._set_attr("showTrailingIcon", value) - - # min_tile_height - @property - def min_tile_height(self) -> OptionalNumber: - return self._get_attr("minTileHeight") - - @min_tile_height.setter - def min_tile_height(self, value: OptionalNumber): - self._set_attr("minTileHeight", value) - - # on_change - @property - def on_change(self): - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - self._set_attr("onChange", True if handler is not None else None) diff --git a/sdk/python/packages/flet/src/flet/core/file_picker.py b/sdk/python/packages/flet/src/flet/core/file_picker.py deleted file mode 100644 index 48aad3a63..000000000 --- a/sdk/python/packages/flet/src/flet/core/file_picker.py +++ /dev/null @@ -1,301 +0,0 @@ -import base64 -import json -from dataclasses import dataclass, field -from enum import Enum -from typing import Any, Callable, List, Optional - -from flet.core.control import Control -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.types import OptionalEventCallable - - -class FilePickerState(Enum): - PICK_FILES = "pickFiles" - SAVE_FILE = "saveFile" - GET_DIRECTORY_PATH = "getDirectoryPath" - - -class FilePickerFileType(Enum): - ANY = "any" - MEDIA = "media" - IMAGE = "image" - VIDEO = "video" - AUDIO = "audio" - CUSTOM = "custom" - - -@dataclass -class FilePickerUploadFile: - name: str - upload_url: str - id: int = None - method: str = field(default="PUT") - - -@dataclass -class FilePickerFile: - name: str - path: str - size: int - id: int - - -class FilePickerResultEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.error = d.get("error") - self.path: Optional[str] = d.get("path") - self.files: Optional[List[FilePickerFile]] = None - files = d.get("files") - if files is not None and isinstance(files, List): - self.files = [] - for fd in files: - self.files.append(FilePickerFile(**fd)) - - -class FilePickerUploadEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.file_name: str = d.get("file_name") - self.progress: Optional[float] = d.get("progress") - self.error: Optional[str] = d.get("error") - - -class FilePicker(Control): - """ - A control that allows you to use the native file explorer to pick single or multiple files, with extensions filtering support and upload. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - def pick_files_result(e: ft.FilePickerResultEvent): - selected_files.value = ( - ", ".join(map(lambda f: f.name, e.files)) if e.files else "Cancelled!" - ) - selected_files.update() - - pick_files_dialog = ft.FilePicker(on_result=pick_files_result) - selected_files = ft.Text() - - page.overlay.append(pick_files_dialog) - - page.add( - ft.Row( - [ - ft.ElevatedButton( - "Pick files", - icon=ft.icons.UPLOAD_FILE, - on_click=lambda _: pick_files_dialog.pick_files( - allow_multiple=True - ), - ), - selected_files, - ] - ) - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/filepicker - """ - - def __init__( - self, - on_result: Optional[Callable[[FilePickerResultEvent], None]] = None, - on_upload: Optional[Callable[[FilePickerUploadEvent], None]] = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - disabled=disabled, - data=data, - ) - - def convert_result_event_data(e): - self.__result = FilePickerResultEvent(e) - return self.__result - - self.__on_result = EventHandler(convert_result_event_data) - self._add_event_handler("result", self.__on_result.get_handler()) - - self.__on_upload = EventHandler(lambda e: FilePickerUploadEvent(e)) - self._add_event_handler("upload", self.__on_upload.get_handler()) - - self.__result: Optional[FilePickerResultEvent] = None - self.__upload: List[FilePickerUploadFile] = [] - self.__allowed_extensions: Optional[List[str]] = None - self.__state = None - self.__file_type = None - self.on_result = on_result - self.on_upload = on_upload - - def _get_control_name(self): - return "filepicker" - - def before_update(self): - super().before_update() - self._set_attr_json("allowedExtensions", self.__allowed_extensions) - self._set_attr_json("upload", self.__upload) - - def pick_files( - self, - dialog_title: Optional[str] = None, - initial_directory: Optional[str] = None, - file_type: FilePickerFileType = FilePickerFileType.ANY, - allowed_extensions: Optional[List[str]] = None, - allow_multiple: Optional[bool] = False, - ): - self.state = FilePickerState.PICK_FILES - self.dialog_title = dialog_title - self.initial_directory = initial_directory - self.file_type = file_type - self.allowed_extensions = allowed_extensions - self.allow_multiple = allow_multiple - self.update() - - def save_file( - self, - dialog_title: Optional[str] = None, - file_name: Optional[str] = None, - initial_directory: Optional[str] = None, - file_type: FilePickerFileType = FilePickerFileType.ANY, - allowed_extensions: Optional[List[str]] = None, - src_bytes: Optional[bytes] = None, - ): - self.state = FilePickerState.SAVE_FILE - self.dialog_title = dialog_title - self.file_name = file_name - self.initial_directory = initial_directory - self.file_type = file_type - self.allowed_extensions = allowed_extensions - self.src_bytes = src_bytes - self.update() - - def get_directory_path( - self, - dialog_title: Optional[str] = None, - initial_directory: Optional[str] = None, - ): - self.state = FilePickerState.GET_DIRECTORY_PATH - self.dialog_title = dialog_title - self.initial_directory = initial_directory - self.update() - - def upload(self, files: List[FilePickerUploadFile]): - self.__upload = files - self.update() - - # state - @property - def state(self) -> Optional[FilePickerState]: - return self.__state - - @state.setter - def state(self, value: Optional[FilePickerState]): - self.__state = value - self._set_enum_attr("state", value, FilePickerState) - - # result - @property - def result(self) -> Optional[FilePickerResultEvent]: - return self.__result - - # dialog_title - @property - def dialog_title(self) -> Optional[str]: - return self._get_attr("dialogTitle") - - @dialog_title.setter - def dialog_title(self, value: Optional[str]): - self._set_attr("dialogTitle", value) - - # file_name - @property - def file_name(self) -> Optional[str]: - return self._get_attr("fileName") - - @file_name.setter - def file_name(self, value: Optional[str]): - self._set_attr("fileName", value) - - # initial_directory - @property - def initial_directory(self): - return self._get_attr("initialDirectory") - - @initial_directory.setter - def initial_directory(self, value: Optional[str]): - self._set_attr("initialDirectory", value) - - # file_type - @property - def file_type(self) -> FilePickerFileType: - return self.__file_type - - @file_type.setter - def file_type(self, value: FilePickerFileType): - self.__file_type = value - self._set_enum_attr("fileType", value, FilePickerFileType) - - # allowed_extensions - @property - def allowed_extensions(self) -> Optional[List[str]]: - return self.__allowed_extensions - - @allowed_extensions.setter - def allowed_extensions(self, value: Optional[List[str]]): - self.__allowed_extensions = value - - # allow_multiple - @property - def allow_multiple(self) -> bool: - return self._get_attr("allowMultiple", data_type="bool", def_value=False) - - @allow_multiple.setter - def allow_multiple(self, value: Optional[bool]): - self._set_attr("allowMultiple", value) - - # src_bytes - @property - def src_bytes(self) -> Optional[bytes]: - v = self._get_attr("srcBytes") - return base64.b64decode(v) if v else v - - @src_bytes.setter - def src_bytes(self, value: Optional[bytes]): - self._set_attr( - "srcBytes", base64.b64encode(value).decode("utf-8") if value else value - ) - - # on_result - @property - def on_result(self) -> OptionalEventCallable[FilePickerResultEvent]: - return self.__on_result.handler - - @on_result.setter - def on_result(self, handler: OptionalEventCallable[FilePickerResultEvent]): - self.__on_result.handler = handler - - # on_upload - @property - def on_upload(self): - return self.__on_upload.handler - - @on_upload.setter - def on_upload(self, handler: OptionalEventCallable[FilePickerUploadEvent]): - self.__on_upload.handler = handler diff --git a/sdk/python/packages/flet/src/flet/core/filled_button.py b/sdk/python/packages/flet/src/flet/core/filled_button.py deleted file mode 100644 index 7312f8216..000000000 --- a/sdk/python/packages/flet/src/flet/core/filled_button.py +++ /dev/null @@ -1,30 +0,0 @@ -from flet.core.elevated_button import ElevatedButton - - -class FilledButton(ElevatedButton): - """ - Filled buttons have the most visual impact after the FloatingActionButton (https://flet.dev/docs/controls/floatingactionbutton), and should be used for important, final actions that complete a flow, like Save, Join now, or Confirm. - - Example: - ``` - import flet as ft - - - def main(page: ft.Page): - page.title = "Basic filled buttons" - page.add( - ft.FilledButton(text="Filled button"), - ft.FilledButton("Disabled button", disabled=True), - ft.FilledButton("Button with icon", icon="add"), - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/filledbutton - """ - - def _get_control_name(self): - return "filledbutton" diff --git a/sdk/python/packages/flet/src/flet/core/filled_tonal_button.py b/sdk/python/packages/flet/src/flet/core/filled_tonal_button.py deleted file mode 100644 index 63dc18239..000000000 --- a/sdk/python/packages/flet/src/flet/core/filled_tonal_button.py +++ /dev/null @@ -1,30 +0,0 @@ -from flet.core.elevated_button import ElevatedButton - - -class FilledTonalButton(ElevatedButton): - """ - A filled tonal button is an alternative middle ground between FilledButton and OutlinedButton buttons. They’re useful in contexts where a lower-priority button requires slightly more emphasis than an outline would give, such as "Next" in an onboarding flow. Tonal buttons use the secondary color mapping. - - Example: - ``` - import flet as ft - - - def main(page: ft.Page): - page.title = "Basic filled tonal buttons" - page.add( - ft.FilledTonalButton(text="Filled tonal button"), - ft.FilledTonalButton("Disabled button", disabled=True), - ft.FilledTonalButton("Button with icon", icon="add"), - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/filledtonalbutton - """ - - def _get_control_name(self): - return "filledtonalbutton" diff --git a/sdk/python/packages/flet/src/flet/core/flashlight.py b/sdk/python/packages/flet/src/flet/core/flashlight.py deleted file mode 100644 index 7a242dcc9..000000000 --- a/sdk/python/packages/flet/src/flet/core/flashlight.py +++ /dev/null @@ -1,90 +0,0 @@ -from typing import Any, Optional - -from flet.core.control import Control -from flet.core.ref import Ref -from flet.utils import deprecated - - -@deprecated( - reason="Flashlight control has been moved to a separate Python package: https://pypi.org/project/flet-flashlight. " - + "Read more about this change in Flet blog: https://flet.dev/blog/flet-v-0-26-release-announcement", - version="0.26.0", - delete_version="0.29.0", -) -class Flashlight(Control): - """ - A control to use FlashLight. Works on iOS and Android. Based on torch_light Flutter widget (https://pub.dev/packages/torch_light). - - Flashlight control is non-visual and should be added to `page.overlay` list. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - flashLight = ft.Flashlight() - page.overlay.append(flashLight) - page.add( - ft.TextButton("toggle", on_click: lambda _: flashlight.toggle()) - ) - - ft.app(target=main) - ``` - - """ - - def __init__( - self, - ref: Optional[Ref] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - data=data, - ) - - self.turned_on = False - - def _get_control_name(self): - return "flashlight" - - def turn_on(self, wait_timeout: Optional[int] = 5) -> bool: - sr = self.invoke_method("on", wait_for_result=True, wait_timeout=wait_timeout) - - if int(sr) == 1: - self.turned_on = True - return self.turned_on - - async def turn_on_async(self, wait_timeout: Optional[int] = 5) -> bool: - sr = await self.invoke_method_async( - "on", wait_for_result=True, wait_timeout=wait_timeout - ) - if int(sr) == 1: - self.turned_on = True - return self.turned_on - - def turn_off(self, wait_timeout: Optional[int] = 5) -> bool: - sr = self.invoke_method("off", wait_for_result=True, wait_timeout=wait_timeout) - - if int(sr) == 1: - self.turned_on = False - return self.turned_on - - async def turn_off_async(self, wait_timeout: Optional[int] = 5) -> bool: - sr = await self.invoke_method_async( - "off", wait_for_result=True, wait_timeout=wait_timeout - ) - if int(sr) == 1: - self.turned_on = False - return self.turned_on - - def toggle(self, wait_timeout: Optional[int] = 5) -> bool: - if self.turned_on: - return self.turn_off(wait_timeout) - return self.turn_on(wait_timeout) - - async def toggle_async(self, wait_timeout: Optional[int] = 5) -> bool: - if self.turned_on: - return await self.turn_off_async(wait_timeout) - return await self.turn_on_async(wait_timeout) diff --git a/sdk/python/packages/flet/src/flet/core/flet_app.py b/sdk/python/packages/flet/src/flet/core/flet_app.py deleted file mode 100644 index da9502404..000000000 --- a/sdk/python/packages/flet/src/flet/core/flet_app.py +++ /dev/null @@ -1,148 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - OffsetValue, - OptionalControlEventCallable, - RotateValue, - ScaleValue, -) - - -class FletApp(ConstrainedControl): - def __init__( - self, - url: Optional[str] = None, - reconnect_interval_ms: Optional[int] = None, - reconnect_timeout_ms: Optional[int] = None, - show_app_startup_screen: Optional[bool] = None, - app_startup_screen_message: Optional[str] = None, - on_error: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.url = url - self.reconnect_interval_ms = reconnect_interval_ms - self.reconnect_timeout_ms = reconnect_timeout_ms - self.show_app_startup_screen = show_app_startup_screen - self.app_startup_screen_message = app_startup_screen_message - self.on_error = on_error - - def _get_control_name(self): - return "fletapp" - - # url - @property - def url(self): - return self._get_attr("url") - - @url.setter - def url(self, value): - self._set_attr("url", value) - - # reconnect_interval_ms - @property - def reconnect_interval_ms(self) -> Optional[int]: - return self._get_attr("reconnectIntervalMs") - - @reconnect_interval_ms.setter - def reconnect_interval_ms(self, value: Optional[int]): - self._set_attr("reconnectIntervalMs", value) - - # reconnect_timeout_ms - @property - def reconnect_timeout_ms(self) -> Optional[int]: - return self._get_attr("reconnectTimeoutMs") - - @reconnect_timeout_ms.setter - def reconnect_timeout_ms(self, value: Optional[int]): - self._set_attr("reconnectTimeoutMs", value) - - # show_app_startup_screen - @property - def show_app_startup_screen(self) -> bool: - return self._get_attr("showAppStartupScreen", data_type="bool", def_value=False) - - @show_app_startup_screen.setter - def show_app_startup_screen(self, value: Optional[bool]): - self._set_attr("showAppStartupScreen", value) - - # app_startup_screen_message - @property - def app_startup_screen_message(self) -> Optional[str]: - return self._get_attr("appStartupScreenMessage") - - @app_startup_screen_message.setter - def app_startup_screen_message(self, value: Optional[str]): - self._set_attr("appStartupScreenMessage", value) - - # on_error - @property - def on_error(self) -> OptionalControlEventCallable: - return self._get_event_handler("error") - - @on_error.setter - def on_error(self, handler: OptionalControlEventCallable): - self._add_event_handler("error", handler) diff --git a/sdk/python/packages/flet/src/flet/core/floating_action_button.py b/sdk/python/packages/flet/src/flet/core/floating_action_button.py deleted file mode 100644 index dd7b9721f..000000000 --- a/sdk/python/packages/flet/src/flet/core/floating_action_button.py +++ /dev/null @@ -1,388 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.buttons import OutlinedBorder -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ClipBehavior, - ColorEnums, - ColorValue, - IconEnums, - IconValue, - MouseCursor, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, - UrlTarget, -) - - -class FloatingActionButton(ConstrainedControl): - """ - A floating action button is a circular icon button that hovers over content to promote a primary action in the application. Floating action button is usually set to `page.floating_action_button`, but can also be added as a regular control at any place on a page. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.title = "Floating Action Button" - page.theme_mode = ft.ThemeMode.LIGHT - page.horizontal_alignment = ft.CrossAxisAlignment.CENTER - page.auto_scroll = True - page.scroll = ft.ScrollMode.HIDDEN - page.appbar = ft.AppBar( - title=ft.Text( - "Floating Action Button", weight=ft.FontWeight.BOLD, color=ft.colors.BLACK87 - ), - bgcolor=ft.colors.BLUE, - center_title=True, - actions=[ - ft.IconButton(ft.icons.MENU, tooltip="Menu", icon_color=ft.colors.BLACK87) - ], - color=ft.colors.WHITE, - ) - - # keeps track of the number of tiles already added - page.count = 0 - - def fab_pressed(e): - page.add(ft.ListTile(title=ft.Text(f"Tile {page.count}"))) - page.show_snack_bar( - ft.SnackBar(ft.Text("Tile was added successfully!"), open=True) - ) - page.count += 1 - - page.floating_action_button = ft.FloatingActionButton( - icon=ft.icons.ADD, on_click=fab_pressed, bgcolor=ft.colors.LIME_300 - ) - page.add(ft.Text("Press the FAB to add a tile!")) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/floatingactionbutton - """ - - def __init__( - self, - text: Optional[str] = None, - icon: Optional[IconValue] = None, - bgcolor: Optional[ColorValue] = None, - content: Optional[Control] = None, - shape: Optional[OutlinedBorder] = None, - autofocus: Optional[bool] = None, - mini: Optional[bool] = None, - foreground_color: Optional[ColorValue] = None, - focus_color: Optional[ColorValue] = None, - clip_behavior: Optional[ClipBehavior] = None, - elevation: OptionalNumber = None, - disabled_elevation: OptionalNumber = None, - focus_elevation: OptionalNumber = None, - highlight_elevation: OptionalNumber = None, - hover_elevation: OptionalNumber = None, - enable_feedback: Optional[bool] = None, - url: Optional[str] = None, - url_target: Optional[UrlTarget] = None, - mouse_cursor: Optional[MouseCursor] = None, - on_click: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.text = text - self.icon = icon - self.bgcolor = bgcolor - self.content = content - self.autofocus = autofocus - self.shape = shape - self.mini = mini - self.url = url - self.url_target = url_target - self.on_click = on_click - self.foreground_color = foreground_color - self.focus_color = focus_color - self.clip_behavior = clip_behavior - self.elevation = elevation - self.disabled_elevation = disabled_elevation - self.focus_elevation = focus_elevation - self.highlight_elevation = highlight_elevation - self.hover_elevation = hover_elevation - self.enable_feedback = enable_feedback - self.mouse_cursor = mouse_cursor - - def _get_control_name(self): - return "floatingactionbutton" - - def before_update(self): - super().before_update() - assert ( - self.text or self.icon or (self.__content and self.__content.visible) - ), "at minimum, text, icon or a visible content must be provided" - self._set_attr_json("shape", self.__shape) - - def _get_children(self): - if self.__content is None: - return [] - self.__content._set_attr_internal("n", "content") - return [self.__content] - - # text - @property - def text(self) -> Optional[str]: - return self._get_attr("text") - - @text.setter - def text(self, value: Optional[str]): - self._set_attr("text", value) - - # icon - @property - def icon(self) -> Optional[IconValue]: - return self.__icon - - @icon.setter - def icon(self, value: Optional[IconValue]): - self.__icon = value - self._set_enum_attr("icon", value, IconEnums) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # url - @property - def url(self) -> Optional[str]: - return self._get_attr("url") - - @url.setter - def url(self, value: Optional[str]): - self._set_attr("url", value) - - # url_target - @property - def url_target(self) -> Optional[UrlTarget]: - return self.__url_target - - @url_target.setter - def url_target(self, value: Optional[UrlTarget]): - self.__url_target = value - self._set_enum_attr("urlTarget", value, UrlTarget) - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self.__mouse_cursor - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self.__mouse_cursor = value - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # shape - @property - def shape(self) -> Optional[OutlinedBorder]: - return self.__shape - - @shape.setter - def shape(self, value: Optional[OutlinedBorder]): - self.__shape = value - - # mini - @property - def mini(self) -> bool: - return self._get_attr("mini", data_type="bool", def_value=False) - - @mini.setter - def mini(self, value: Optional[bool]): - self._set_attr("mini", value) - - # elevation - @property - def elevation(self) -> OptionalNumber: - return self._get_attr("elevation", data_type="float") - - @elevation.setter - def elevation(self, value: OptionalNumber): - assert value is None or value >= 0, "elevation cannot be negative" - self._set_attr("elevation", value) - - # disabled_elevation - @property - def disabled_elevation(self) -> OptionalNumber: - return self._get_attr("disabledElevation", data_type="float") - - @disabled_elevation.setter - def disabled_elevation(self, value: OptionalNumber): - assert value is None or value >= 0, "disabled_elevation cannot be negative" - self._set_attr("disabledElevation", value) - - # enable_feedback - @property - def enable_feedback(self) -> bool: - return self._get_attr("enableFeedback", data_type="bool", def_value=True) - - @enable_feedback.setter - def enable_feedback(self, value: Optional[bool]): - self._set_attr("enableFeedback", value) - - # focus_color - @property - def focus_color(self) -> Optional[ColorValue]: - return self.__focus_color - - @focus_color.setter - def focus_color(self, value: Optional[ColorValue]): - self.__focus_color = value - self._set_enum_attr("focusColor", value, ColorEnums) - - # focus_elevation - @property - def focus_elevation(self) -> OptionalNumber: - return self._get_attr("focusElevation", data_type="float") - - @focus_elevation.setter - def focus_elevation(self, value: OptionalNumber): - assert value is None or value >= 0, "focus_elevation cannot be negative" - self._set_attr("focusElevation", value) - - # foreground_color - @property - def foreground_color(self) -> Optional[ColorValue]: - return self.__foreground_color - - @foreground_color.setter - def foreground_color(self, value: Optional[ColorValue]): - self.__foreground_color = value - self._set_enum_attr("foregroundColor", value, ColorEnums) - - # highlight_elevation - @property - def highlight_elevation(self) -> OptionalNumber: - return self._get_attr("highlightElevation", data_type="float") - - @highlight_elevation.setter - def highlight_elevation(self, value: OptionalNumber): - assert value is None or value >= 0, "highlight_elevation cannot be negative" - self._set_attr("highlightElevation", value) - - # hover_elevation - @property - def hover_elevation(self) -> OptionalNumber: - return self._get_attr("hoverElevation", data_type="float") - - @hover_elevation.setter - def hover_elevation(self, value: OptionalNumber): - assert value is None or value >= 0, "hover_elevation cannot be negative" - self._set_attr("hoverElevation", value) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) diff --git a/sdk/python/packages/flet/src/flet/core/form_field_control.py b/sdk/python/packages/flet/src/flet/core/form_field_control.py deleted file mode 100644 index 51ccc4fee..000000000 --- a/sdk/python/packages/flet/src/flet/core/form_field_control.py +++ /dev/null @@ -1,734 +0,0 @@ -from enum import Enum -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.box import BoxConstraints -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - BorderRadiusValue, - ColorEnums, - ColorValue, - DurationValue, - IconEnums, - IconValueOrControl, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, - VerticalAlignment, -) - -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal - - -class InputBorder(Enum): - NONE = "none" - OUTLINE = "outline" - UNDERLINE = "underline" - - -class FormFieldControl(ConstrainedControl): - def __init__( - self, - text_size: OptionalNumber = None, - text_style: Optional[TextStyle] = None, - text_vertical_align: Union[VerticalAlignment, OptionalNumber] = None, - label: Optional[Union[str, Control]] = None, - label_style: Optional[TextStyle] = None, - icon: Optional[IconValueOrControl] = None, - border: Optional[InputBorder] = None, - color: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, - border_radius: Optional[BorderRadiusValue] = None, - border_width: OptionalNumber = None, - border_color: Optional[ColorValue] = None, - focused_color: Optional[ColorValue] = None, - focused_bgcolor: Optional[ColorValue] = None, - focused_border_width: OptionalNumber = None, - focused_border_color: Optional[ColorValue] = None, - content_padding: Optional[PaddingValue] = None, - dense: Optional[bool] = None, - filled: Optional[bool] = None, - fill_color: Optional[ColorValue] = None, - focus_color: Optional[ColorValue] = None, - align_label_with_hint: Optional[bool] = None, - hover_color: Optional[ColorValue] = None, - hint_text: Optional[str] = None, - hint_style: Optional[TextStyle] = None, - hint_fade_duration: Optional[DurationValue] = None, - hint_max_lines: Optional[int] = None, - helper: Optional[Control] = None, - helper_text: Optional[str] = None, - helper_style: Optional[TextStyle] = None, - helper_max_lines: Optional[int] = None, - counter: Optional[Control] = None, - counter_text: Optional[str] = None, - counter_style: Optional[TextStyle] = None, - error: Optional[Control] = None, - error_text: Optional[str] = None, - error_style: Optional[TextStyle] = None, - error_max_lines: Optional[int] = None, - prefix: Optional[Control] = None, - prefix_icon: Optional[IconValueOrControl] = None, - prefix_icon_size_constraints: Optional[BoxConstraints] = None, - prefix_text: Optional[str] = None, - prefix_style: Optional[TextStyle] = None, - suffix: Optional[Control] = None, - suffix_icon: Optional[IconValueOrControl] = None, - suffix_icon_size_constraints: Optional[BoxConstraints] = None, - size_constraints: Optional[BoxConstraints] = None, - collapsed: Optional[bool] = None, - fit_parent_size: Optional[bool] = None, - suffix_text: Optional[str] = None, - suffix_style: Optional[TextStyle] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - rtl=rtl, - ) - - self.text_size = text_size - self.text_style = text_style - self.text_vertical_align = text_vertical_align - self.label = label - self.label_style = label_style - self.icon = icon - self.border = border - self.color = color - self.bgcolor = bgcolor - self.border_radius = border_radius - self.border_width = border_width - self.border_color = border_color - self.focused_color = focused_color - self.focused_bgcolor = focused_bgcolor - self.focused_border_width = focused_border_width - self.focused_border_color = focused_border_color - self.content_padding = content_padding - self.filled = filled - self.dense = dense - self.hint_text = hint_text - self.hint_style = hint_style - self.helper = helper - self.helper_text = helper_text - self.helper_style = helper_style - self.counter = counter - self.counter_text = counter_text - self.counter_style = counter_style - self.error = error - self.error_text = error_text - self.error_style = error_style - self.prefix = prefix - self.prefix_icon = prefix_icon - self.prefix_text = prefix_text - self.prefix_style = prefix_style - self.suffix = suffix - self.suffix_icon = suffix_icon - self.suffix_text = suffix_text - self.suffix_style = suffix_style - self.hover_color = hover_color - self.fill_color = fill_color - self.focus_color = focus_color - self.align_label_with_hint = align_label_with_hint - self.hint_fade_duration = hint_fade_duration - self.hint_max_lines = hint_max_lines - self.helper_max_lines = helper_max_lines - self.error_max_lines = error_max_lines - self.prefix_icon_size_constraints = prefix_icon_size_constraints - self.suffix_icon_size_constraints = suffix_icon_size_constraints - self.size_constraints = size_constraints - self.collapsed = collapsed - self.fit_parent_size = fit_parent_size - - def before_update(self): - super().before_update() - self._set_attr_json("borderRadius", self.__border_radius) - self._set_attr_json("contentPadding", self.__content_padding) - self._set_attr_json("textStyle", self.__text_style) - self._set_attr_json("labelStyle", self.__label_style) - self._set_attr_json("hintStyle", self.__hint_style) - self._set_attr_json("helperStyle", self.__helper_style) - self._set_attr_json("counterStyle", self.__counter_style) - self._set_attr_json("errorStyle", self.__error_style) - self._set_attr_json("prefixStyle", self.__prefix_style) - self._set_attr_json("suffixStyle", self.__suffix_style) - self._set_attr_json("hintFadeDuration", self.__hint_fade_duration) - self._set_attr_json( - "prefixIconSizeConstraints", self.__prefix_icon_size_constraints - ) - self._set_attr_json( - "suffixIconSizeConstraints", self.__suffix_icon_size_constraints - ) - self._set_attr_json("sizeConstraints", self.__size_constraints) - if isinstance(self.__suffix_icon, str): - self._set_attr("suffixIcon", self.__suffix_icon) - if isinstance(self.__prefix_icon, str): - self._set_attr("prefixIcon", self.__prefix_icon) - if isinstance(self.__icon, str): - self._set_attr("icon", self.__icon) - if isinstance(self.__label, str): - self._set_attr("label", self.__label) - - def _get_children(self): - children = [] - for control, name in [ - (self.__prefix, "prefix"), - (self.__suffix, "suffix"), - (self.__suffix_icon, "suffix_icon"), - (self.__prefix_icon, "prefix_icon"), - (self.__icon, "icon"), - (self.__counter, "counter"), - (self.__error, "error"), - (self.__helper, "helper"), - (self.__label, "label"), - ]: - if isinstance(control, Control): - control._set_attr_internal("n", name) - children.append(control) - return children - - # text_size - @property - def text_size(self) -> OptionalNumber: - return self._get_attr("textSize") - - @text_size.setter - def text_size(self, value: OptionalNumber): - self._set_attr("textSize", value) - - # text_style - @property - def text_style(self) -> Optional[TextStyle]: - return self.__text_style - - @text_style.setter - def text_style(self, value: Optional[TextStyle]): - self.__text_style = value - - # label - @property - def label(self) -> Optional[Union[str, Control]]: - return self.__label - - @label.setter - def label(self, value: Optional[Union[str, Control]]): - self.__label = value - - # label_style - @property - def label_style(self) -> Optional[TextStyle]: - return self.__label_style - - @label_style.setter - def label_style(self, value: Optional[TextStyle]): - self.__label_style = value - - # icon - @property - def icon(self) -> Optional[IconValueOrControl]: - return self.__icon - - @icon.setter - def icon(self, value: Optional[IconValueOrControl]): - self.__icon = value - if not isinstance(value, Control): - self._set_enum_attr("icon", value, IconEnums) - - # border - @property - def border(self) -> Optional[InputBorder]: - return self.__border - - @border.setter - def border(self, value: Optional[InputBorder]): - self.__border = value - self._set_enum_attr("border", value, InputBorder) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # focus_color - @property - def focus_color(self) -> Optional[str]: - return self._get_attr("focusColor") - - @focus_color.setter - def focus_color(self, value: Optional[str]): - self._set_attr("focusColor", value) - - # align_label_with_hint - @property - def align_label_with_hint(self) -> Optional[bool]: - return self._get_attr("alignLabelWithHint", data_type="bool") - - @align_label_with_hint.setter - def align_label_with_hint(self, value: Optional[bool]): - self._set_attr("alignLabelWithHint", value) - - # fit_parent_size - @property - def fit_parent_size(self) -> Optional[bool]: - return self._get_attr("fitParentSize", data_type="bool", def_value=False) - - @fit_parent_size.setter - def fit_parent_size(self, value: Optional[bool]): - self._set_attr("fitParentSize", value) - - # hint_fade_duration - @property - def hint_fade_duration(self) -> Optional[DurationValue]: - return self.__hint_fade_duration - - @hint_fade_duration.setter - def hint_fade_duration(self, value: Optional[DurationValue]): - self.__hint_fade_duration = value - - # hint_max_lines - @property - def hint_max_lines(self) -> Optional[int]: - return self._get_attr("hintMaxLines", data_type="int") - - @hint_max_lines.setter - def hint_max_lines(self, value: Optional[int]): - self._set_attr("hintMaxLines", value) - - # helper_max_lines - @property - def helper_max_lines(self) -> Optional[int]: - return self._get_attr("helperMaxLines", data_type="int") - - @helper_max_lines.setter - def helper_max_lines(self, value: Optional[int]): - self._set_attr("helperMaxLines", value) - - # error_max_lines - @property - def error_max_lines(self) -> Optional[int]: - return self._get_attr("errorMaxLines", data_type="int") - - @error_max_lines.setter - def error_max_lines(self, value: Optional[int]): - self._set_attr("errorMaxLines", value) - - # prefix_icon_size_constraints - @property - def prefix_icon_size_constraints(self) -> Optional[BoxConstraints]: - return self.__prefix_icon_size_constraints - - @prefix_icon_size_constraints.setter - def prefix_icon_size_constraints(self, value: Optional[BoxConstraints]): - self.__prefix_icon_size_constraints = value - - # suffix_icon_size_constraints - @property - def suffix_icon_size_constraints(self) -> Optional[BoxConstraints]: - return self.__suffix_icon_size_constraints - - @suffix_icon_size_constraints.setter - def suffix_icon_size_constraints(self, value: Optional[BoxConstraints]): - self.__suffix_icon_size_constraints = value - - # size_constraints - @property - def size_constraints(self) -> Optional[BoxConstraints]: - return self.__size_constraints - - @size_constraints.setter - def size_constraints(self, value: Optional[BoxConstraints]): - self.__size_constraints = value - - # collapsed - @property - def collapsed(self) -> Optional[bool]: - return self._get_attr("collapsed", data_type="bool") - - @collapsed.setter - def collapsed(self, value: Optional[bool]): - self._set_attr("collapsed", value) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # border_radius - @property - def border_radius(self) -> Optional[BorderRadiusValue]: - return self.__border_radius - - @border_radius.setter - def border_radius(self, value: Optional[BorderRadiusValue]): - self.__border_radius = value - - # border_width - @property - def border_width(self) -> OptionalNumber: - return self._get_attr("borderWidth") - - @border_width.setter - def border_width(self, value: OptionalNumber): - self._set_attr("borderWidth", value) - - # border_color - @property - def border_color(self) -> Optional[ColorValue]: - return self.__border_color - - @border_color.setter - def border_color(self, value: Optional[ColorValue]): - self.__border_color = value - self._set_enum_attr("borderColor", value, ColorEnums) - - # text_vertical_align - @property - def text_vertical_align(self) -> Union[VerticalAlignment, OptionalNumber]: - return self._get_attr("textVerticalAlign") - - @text_vertical_align.setter - def text_vertical_align(self, value: Union[VerticalAlignment, OptionalNumber]): - v = value.value if isinstance(value, VerticalAlignment) else value - if v is not None: - v = max(-1.0, min(v, 1.0)) # make sure 0.0 <= value <= 1.0 - self._set_attr("textVerticalAlign", v) - - # focused_color - @property - def focused_color(self) -> Optional[ColorValue]: - return self.__focused_color - - @focused_color.setter - def focused_color(self, value: Optional[ColorValue]): - self.__focused_color = value - self._set_enum_attr("focusedColor", value, ColorEnums) - - # focused_bgcolor - @property - def focused_bgcolor(self) -> Optional[str]: - return self.__focused_bgcolor - - @focused_bgcolor.setter - def focused_bgcolor(self, value: Optional[str]): - self.__focused_bgcolor = value - self._set_enum_attr("focusedBgcolor", value, ColorEnums) - - # focused_border_width - @property - def focused_border_width(self) -> OptionalNumber: - return self._get_attr("focusedBorderWidth") - - @focused_border_width.setter - def focused_border_width(self, value: OptionalNumber): - self._set_attr("focusedBorderWidth", value) - - # focused_border_color - @property - def focused_border_color(self) -> Optional[ColorValue]: - return self.__focused_border_color - - @focused_border_color.setter - def focused_border_color(self, value: Optional[ColorValue]): - self.__focused_border_color = value - self._set_enum_attr("focusedBorderColor", value, ColorEnums) - - # content_padding - @property - def content_padding(self) -> Optional[PaddingValue]: - return self.__content_padding - - @content_padding.setter - def content_padding(self, value: Optional[PaddingValue]): - self.__content_padding = value - - # dense - @property - def dense(self) -> bool: - return self._get_attr("dense", data_type="bool", def_value=False) - - @dense.setter - def dense(self, value: Optional[bool]): - self._set_attr("dense", value) - - # filled - @property - def filled(self) -> Optional[bool]: - return self._get_attr("filled", data_type="bool") - - @filled.setter - def filled(self, value: Optional[bool]): - self._set_attr("filled", value) - - # hint_text - @property - def hint_text(self) -> Optional[str]: - return self._get_attr("hintText") - - @hint_text.setter - def hint_text(self, value: Optional[str]): - self._set_attr("hintText", value) - - # hint_style - @property - def hint_style(self) -> Optional[TextStyle]: - return self.__hint_style - - @hint_style.setter - def hint_style(self, value: Optional[TextStyle]): - self.__hint_style = value - - # helper_text - @property - def helper_text(self) -> Optional[str]: - return self._get_attr("helperText") - - @helper_text.setter - def helper_text(self, value: Optional[str]): - self._set_attr("helperText", value) - - # helper_style - @property - def helper_style(self) -> Optional[TextStyle]: - return self.__helper_style - - @helper_style.setter - def helper_style(self, value: Optional[TextStyle]): - self.__helper_style = value - - # counter_text - @property - def counter_text(self) -> Optional[str]: - return self._get_attr("counterText") - - @counter_text.setter - def counter_text(self, value: Optional[str]): - self._set_attr("counterText", value) - - # counter_style - @property - def counter_style(self) -> Optional[TextStyle]: - return self.__counter_style - - @counter_style.setter - def counter_style(self, value: Optional[TextStyle]): - self.__counter_style = value - - # error_text - @property - def error_text(self) -> Optional[str]: - return self._get_attr("errorText") - - @error_text.setter - def error_text(self, value: Optional[str]): - self._set_attr("errorText", value) - - # error_style - @property - def error_style(self) -> Optional[TextStyle]: - return self.__error_style - - @error_style.setter - def error_style(self, value: Optional[TextStyle]): - self.__error_style = value - - # prefix - @property - def prefix(self) -> Optional[Control]: - return self.__prefix - - @prefix.setter - def prefix(self, value: Optional[Control]): - self.__prefix = value - - # error - @property - def error(self) -> Optional[Control]: - return self.__error - - @error.setter - def error(self, value: Optional[Control]): - self.__error = value - - # helper - @property - def helper(self) -> Optional[Control]: - return self.__helper - - @helper.setter - def helper(self, value: Optional[Control]): - self.__helper = value - - # counter - @property - def counter(self) -> Optional[Control]: - return self.__counter - - @counter.setter - def counter(self, value: Optional[Control]): - self.__counter = value - - # prefix_icon - @property - def prefix_icon(self) -> Optional[IconValueOrControl]: - return self.__prefix_icon - - @prefix_icon.setter - def prefix_icon(self, value: Optional[IconValueOrControl]): - self.__prefix_icon = value - if not isinstance(value, Control): - self._set_enum_attr("prefixIcon", value, IconEnums) - - # prefix_text - @property - def prefix_text(self) -> Optional[str]: - return self._get_attr("prefixText") - - @prefix_text.setter - def prefix_text(self, value: Optional[str]): - self._set_attr("prefixText", value) - - # prefix_style - @property - def prefix_style(self) -> Optional[TextStyle]: - return self.__prefix_style - - @prefix_style.setter - def prefix_style(self, value: Optional[TextStyle]): - self.__prefix_style = value - - # suffix - @property - def suffix(self) -> Optional[Control]: - return self.__suffix - - @suffix.setter - def suffix(self, value: Optional[Control]): - self.__suffix = value - - # suffix_icon - @property - def suffix_icon(self) -> Optional[IconValueOrControl]: - return self.__suffix_icon - - @suffix_icon.setter - def suffix_icon(self, value: Optional[IconValueOrControl]): - self.__suffix_icon = value - if not isinstance(value, Control): - self._set_enum_attr("suffixIcon", value, IconEnums) - - # suffix_text - @property - def suffix_text(self) -> Optional[str]: - return self._get_attr("suffixText") - - @suffix_text.setter - def suffix_text(self, value: Optional[str]): - self._set_attr("suffixText", value) - - # suffix_style - @property - def suffix_style(self) -> Optional[TextStyle]: - return self.__suffix_style - - @suffix_style.setter - def suffix_style(self, value: Optional[TextStyle]): - self.__suffix_style = value - - # fill_color - @property - def fill_color(self) -> Optional[ColorValue]: - return self.__fill_color - - @fill_color.setter - def fill_color(self, value: Optional[ColorValue]): - self.__fill_color = value - self._set_enum_attr("fillColor", value, ColorEnums) - - # hover_color - @property - def hover_color(self) -> Optional[ColorValue]: - return self.__hover_color - - @hover_color.setter - def hover_color(self, value: Optional[ColorValue]): - self.__hover_color = value - self._set_enum_attr("hoverColor", value, ColorEnums) diff --git a/sdk/python/packages/flet/src/flet/core/geolocator.py b/sdk/python/packages/flet/src/flet/core/geolocator.py deleted file mode 100644 index 439327dfe..000000000 --- a/sdk/python/packages/flet/src/flet/core/geolocator.py +++ /dev/null @@ -1,347 +0,0 @@ -import json -from dataclasses import dataclass -from enum import Enum -from typing import Any, Optional - -from flet.core.control import Control -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.types import ( - ColorValue, - DurationValue, - OptionalControlEventCallable, - OptionalEventCallable, - OptionalNumber, -) -from flet.utils import deprecated - - -class GeolocatorPositionAccuracy(Enum): - LOWEST = "lowest" - LOW = "low" - MEDIUM = "medium" - HIGH = "high" - BEST = "best" - BEST_FOR_NAVIGATION = "bestForNavigation" - REDUCED = "reduced" - - -class GeolocatorPermissionStatus(Enum): - DENIED = "denied" - DENIED_FOREVER = "deniedForever" - WHILE_IN_USE = "whileInUse" - ALWAYS = "always" - UNABLE_TO_DETERMINE = "unableToDetermine" - - -class GeolocatorActivityType(Enum): - AUTOMOTIVE_NAVIGATION = "automotiveNavigation" - FITNESS = "fitness" - OTHER_NAVIGATION = "otherNavigation" - AIRBORNE = "airborne" - OTHER = "other" - - -@dataclass -class GeolocatorPosition: - latitude: OptionalNumber = None - longitude: OptionalNumber = None - speed: OptionalNumber = None - altitude: OptionalNumber = None - timestamp: OptionalNumber = None - accuracy: OptionalNumber = None - altitude_accuracy: OptionalNumber = None - heading: OptionalNumber = None - heading_accuracy: OptionalNumber = None - speed_accuracy: OptionalNumber = None - floor: Optional[int] = None - is_mocked: Optional[bool] = None - - -@dataclass -class GeolocatorSettings: - accuracy: Optional[GeolocatorPositionAccuracy] = None - distance_filter: Optional[int] = None - time_limit: Optional[DurationValue] = None - - -@dataclass -class GeolocatorWebSettings(GeolocatorSettings): - maximum_age: Optional[DurationValue] = None - - -@dataclass -class GeolocatorAppleSettings(GeolocatorSettings): - activity_type: Optional[GeolocatorActivityType] = None - pause_location_updates_automatically: Optional[bool] = False - show_background_location_indicator: Optional[bool] = False - allow_background_location_updates: Optional[bool] = True - - -@dataclass -class GeolocatorAndroidSettings(GeolocatorSettings): - force_location_manager: Optional[bool] = False - interval_duration: Optional[DurationValue] = False - foreground_notification_text: Optional[str] = None - foreground_notification_title: Optional[str] = None - foreground_notification_channel_name: Optional[str] = "Background Location" - foreground_notification_enable_wake_lock: Optional[bool] = False - foreground_notification_enable_wifi_lock: Optional[bool] = False - foreground_notification_set_ongoing: Optional[bool] = False - foreground_notification_color: Optional[ColorValue] = None - - -class GeolocatorPositionChangeEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.latitude: float = d.get("lat") - self.longitude: float = d.get("long") - - -@deprecated( - reason="Geolocator control has been moved to a separate Python package: https://pypi.org/project/flet-geolocator. " - + "Read more about this change in Flet blog: https://flet.dev/blog/flet-v-0-26-release-announcement", - version="0.26.0", - delete_version="0.29.0", -) -class Geolocator(Control): - """ - A control that allows you to fetch GPS data from your device. - This control is non-visual and should be added to `page.overlay` list. - - ----- - - Online docs: https://flet.dev/docs/controls/geolocator - """ - - def __init__( - self, - # Control - # - ref: Optional[Ref] = None, - data: Any = None, - location_settings: Optional[GeolocatorSettings] = None, - on_position_change: OptionalEventCallable[GeolocatorPositionChangeEvent] = None, - on_error: OptionalControlEventCallable = None, - ): - Control.__init__( - self, - ref=ref, - data=data, - ) - self.__on_position_change = EventHandler( - lambda e: GeolocatorPositionChangeEvent(e) - ) - self._add_event_handler( - "positionChange", self.__on_position_change.get_handler() - ) - self.on_position_change = on_position_change - self.on_error = on_error - self.location_settings = location_settings - - def _get_control_name(self): - return "geolocator" - - def before_update(self): - self._set_attr_json("locationSettings", self.location_settings) - - def get_current_position( - self, - accuracy: Optional[ - GeolocatorPositionAccuracy - ] = GeolocatorPositionAccuracy.BEST, - location_settings: Optional[GeolocatorSettings] = None, - wait_timeout: Optional[float] = 25, - ) -> GeolocatorPosition: - ls = ( - location_settings - or self.location_settings - or GeolocatorSettings(accuracy=accuracy) - ) - output = self.invoke_method( - "get_current_position", - {"location_settings": self._convert_attr_json(ls)}, - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return ( - GeolocatorPosition(**json.loads(output)) - if output is not None - else GeolocatorPosition() - ) - - async def get_current_position_async( - self, - accuracy: Optional[ - GeolocatorPositionAccuracy - ] = GeolocatorPositionAccuracy.BEST, - location_settings: Optional[GeolocatorSettings] = None, - wait_timeout: Optional[float] = 25, - ) -> GeolocatorPosition: - ls = ( - location_settings - or self.location_settings - or GeolocatorSettings(accuracy=accuracy) - ) - output = await self.invoke_method_async( - "get_current_position", - {"location_settings": self._convert_attr_json(ls)}, - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return ( - GeolocatorPosition(**json.loads(output)) - if output is not None - else GeolocatorPosition() - ) - - def get_last_known_position( - self, - wait_timeout: Optional[float] = 25, - ) -> GeolocatorPosition: - assert not self.page.web, "get_last_known_position is not supported on web" - output = self.invoke_method( - "get_last_known_position", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return ( - GeolocatorPosition(**json.loads(output)) - if output is not None - else GeolocatorPosition() - ) - - async def get_last_known_position_async( - self, - wait_timeout: Optional[float] = 25, - ) -> GeolocatorPosition: - assert not self.page.web, "get_last_known_position is not supported on web" - output = await self.invoke_method_async( - "get_last_known_position", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return ( - GeolocatorPosition(**json.loads(output)) - if output is not None - else GeolocatorPosition() - ) - - def get_permission_status( - self, wait_timeout: Optional[float] = 25 - ) -> GeolocatorPermissionStatus: - p = self.invoke_method( - "get_permission_status", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return GeolocatorPermissionStatus(p) - - async def get_permission_status_async( - self, wait_timeout: Optional[float] = 25 - ) -> GeolocatorPermissionStatus: - p = await self.invoke_method_async( - "get_permission_status", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return GeolocatorPermissionStatus(p) - - def request_permission( - self, wait_timeout: Optional[float] = 25 - ) -> GeolocatorPermissionStatus: - p = self.invoke_method( - "request_permission", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return GeolocatorPermissionStatus(p) - - async def request_permission_async( - self, wait_timeout: Optional[float] = 25 - ) -> GeolocatorPermissionStatus: - p = await self.invoke_method_async( - "request_permission", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return GeolocatorPermissionStatus(p) - - def is_location_service_enabled(self, wait_timeout: Optional[float] = 10) -> bool: - enabled = self.invoke_method( - "is_location_service_enabled", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return enabled == "true" - - async def is_location_service_enabled_async( - self, wait_timeout: Optional[float] = 10 - ) -> bool: - enabled = await self.invoke_method_async( - "is_location_service_enabled", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return enabled == "true" - - def open_app_settings(self, wait_timeout: Optional[float] = 10) -> bool: - assert not self.page.web, "open_app_settings is not supported on web" - opened = self.invoke_method( - "open_app_settings", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return opened == "true" - - async def open_app_settings_async(self, wait_timeout: Optional[float] = 10) -> bool: - assert not self.page.web, "open_app_settings is not supported on web" - opened = await self.invoke_method_async( - "open_app_settings", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return opened == "true" - - def open_location_settings(self, wait_timeout: Optional[float] = 10) -> bool: - assert not self.page.web, "open_location_settings is not supported on web" - opened = self.invoke_method( - "open_location_settings", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return opened == "true" - - async def open_location_settings_async( - self, wait_timeout: Optional[float] = 10 - ) -> bool: - assert not self.page.web, "open_location_settings is not supported on web" - opened = await self.invoke_method_async( - "open_location_settings", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return opened == "true" - - @property - def on_position_change( - self, - ) -> OptionalEventCallable[GeolocatorPositionChangeEvent]: - return self.__on_position_change.handler - - @on_position_change.setter - def on_position_change( - self, handler: OptionalEventCallable[GeolocatorPositionChangeEvent] - ): - self.__on_position_change.handler = handler - self._set_attr("onPositionChange", True if handler is not None else None) - - @property - def on_error(self) -> OptionalControlEventCallable: - return self._get_attr("error") - - @on_error.setter - def on_error(self, handler: OptionalControlEventCallable): - self._add_event_handler("error", handler) diff --git a/sdk/python/packages/flet/src/flet/core/gesture_detector.py b/sdk/python/packages/flet/src/flet/core/gesture_detector.py deleted file mode 100644 index 1e7010531..000000000 --- a/sdk/python/packages/flet/src/flet/core/gesture_detector.py +++ /dev/null @@ -1,870 +0,0 @@ -import json -from typing import Any, Optional, Set, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.types import ( - MouseCursor, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - PointerDeviceType, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class GestureDetector(ConstrainedControl, AdaptiveControl): - """ - A control that detects gestures. - - Attempts to recognize gestures that correspond to its non-null callbacks. - - If this control has a content, it defers to that child control for its sizing behavior. If it does not have a content, it grows to fit the parent instead. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - def on_pan_update1(e: ft.DragUpdateEvent): - c.top = max(0, c.top + e.delta_y) - c.left = max(0, c.left + e.delta_x) - c.update() - - def on_pan_update2(e: ft.DragUpdateEvent): - e.control.top = max(0, e.control.top + e.delta_y) - e.control.left = max(0, e.control.left + e.delta_x) - e.control.update() - - gd = ft.GestureDetector( - mouse_cursor=ft.MouseCursor.MOVE, - drag_interval=50, - on_pan_update=on_pan_update1, - ) - - c = ft.Container(gd, bgcolor=ft.colors.AMBER, width=50, height=50, left=0, top=0) - - gd1 = ft.GestureDetector( - mouse_cursor=ft.MouseCursor.MOVE, - drag_interval=10, - on_vertical_drag_update=on_pan_update2, - left=100, - top=100, - content=ft.Container(bgcolor=ft.colors.BLUE, width=50, height=50), - ) - - page.add( ft.Stack([c, gd1], width=1000, height=500)) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/gesturedetector - """ - - def __init__( - self, - content: Optional[Control] = None, - mouse_cursor: Optional[MouseCursor] = None, - drag_interval: Optional[int] = None, - hover_interval: Optional[int] = None, - exclude_from_semantics: Optional[bool] = None, - trackpad_scroll_causes_scale: Optional[bool] = None, - allowed_devices: Optional[Set[PointerDeviceType]] = None, - on_tap: OptionalEventCallable["TapEvent"] = None, - on_tap_down: OptionalEventCallable["TapEvent"] = None, - on_tap_up: OptionalEventCallable["TapEvent"] = None, - on_multi_tap: OptionalEventCallable["TapEvent"] = None, - multi_tap_touches: Optional[int] = None, - on_multi_long_press: OptionalEventCallable["LongPressEndEvent"] = None, - on_secondary_tap: OptionalEventCallable["TapEvent"] = None, - on_secondary_tap_down: OptionalEventCallable["TapEvent"] = None, - on_secondary_tap_up: OptionalEventCallable["TapEvent"] = None, - on_long_press_start: OptionalEventCallable["LongPressEndEvent"] = None, - on_long_press_end: OptionalEventCallable["LongPressEndEvent"] = None, - on_secondary_long_press_start: OptionalEventCallable[ - "LongPressEndEvent" - ] = None, - on_secondary_long_press_end: OptionalEventCallable["LongPressEndEvent"] = None, - on_double_tap: OptionalEventCallable["TapEvent"] = None, - on_double_tap_down: OptionalEventCallable["TapEvent"] = None, - on_horizontal_drag_start: OptionalEventCallable["DragStartEvent"] = None, - on_horizontal_drag_update: OptionalEventCallable["DragUpdateEvent"] = None, - on_horizontal_drag_end: OptionalEventCallable["DragEndEvent"] = None, - on_vertical_drag_start: OptionalEventCallable["DragStartEvent"] = None, - on_vertical_drag_update: OptionalEventCallable["DragUpdateEvent"] = None, - on_vertical_drag_end: OptionalEventCallable["DragEndEvent"] = None, - on_pan_start: OptionalEventCallable["DragStartEvent"] = None, - on_pan_update: OptionalEventCallable["DragUpdateEvent"] = None, - on_pan_end: OptionalEventCallable["DragEndEvent"] = None, - on_scale_start: OptionalEventCallable["ScaleStartEvent"] = None, - on_scale_update: OptionalEventCallable["ScaleUpdateEvent"] = None, - on_scale_end: OptionalEventCallable["ScaleEndEvent"] = None, - on_hover: OptionalEventCallable["HoverEvent"] = None, - on_enter: OptionalEventCallable["HoverEvent"] = None, - on_exit: OptionalEventCallable["HoverEvent"] = None, - on_scroll: OptionalEventCallable["ScrollEvent"] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - # - # AdaptiveControl - # - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.__on_tap_down = EventHandler(lambda e: TapEvent(e)) - self._add_event_handler("tap_down", self.__on_tap_down.get_handler()) - - self.__on_tap_up = EventHandler(lambda e: TapEvent(e)) - self._add_event_handler("tap_up", self.__on_tap_up.get_handler()) - - self.__on_multi_tap = EventHandler(lambda e: MultiTapEvent(e)) - self._add_event_handler("multi_tap", self.__on_multi_tap.get_handler()) - - self.__on_secondary_tap_down = EventHandler(lambda e: TapEvent(e)) - self._add_event_handler( - "secondary_tap_down", self.__on_secondary_tap_down.get_handler() - ) - - self.__on_secondary_tap_up = EventHandler(lambda e: TapEvent(e)) - self._add_event_handler( - "secondary_tap_up", self.__on_secondary_tap_up.get_handler() - ) - - self.__on_long_press_start = EventHandler(lambda e: LongPressStartEvent(e)) - self._add_event_handler( - "long_press_start", self.__on_long_press_start.get_handler() - ) - - self.__on_long_press_end = EventHandler(lambda e: LongPressEndEvent(e)) - self._add_event_handler( - "long_press_end", self.__on_long_press_end.get_handler() - ) - - self.__on_secondary_long_press_start = EventHandler( - lambda e: LongPressStartEvent(e) - ) - self._add_event_handler( - "secondary_long_press_start", - self.__on_secondary_long_press_start.get_handler(), - ) - - self.__on_secondary_long_press_end = EventHandler( - lambda e: LongPressEndEvent(e) - ) - self._add_event_handler( - "secondary_long_press_end", - self.__on_secondary_long_press_end.get_handler(), - ) - self.__on_double_tap_down = EventHandler(lambda e: TapEvent(e)) - self._add_event_handler( - "double_tap_down", self.__on_double_tap_down.get_handler() - ) - - # on_horizontal_drag - - self.__on_horizontal_drag_start = EventHandler(lambda e: DragStartEvent(e)) - self._add_event_handler( - "horizontal_drag_start", self.__on_horizontal_drag_start.get_handler() - ) - self.__on_horizontal_drag_update = EventHandler(lambda e: DragUpdateEvent(e)) - self._add_event_handler( - "horizontal_drag_update", self.__on_horizontal_drag_update.get_handler() - ) - self.__on_horizontal_drag_end = EventHandler(lambda e: DragEndEvent(e)) - self._add_event_handler( - "horizontal_drag_end", self.__on_horizontal_drag_end.get_handler() - ) - - # on_vertical_drag - - self.__on_vertical_drag_start = EventHandler(lambda e: DragStartEvent(e)) - self._add_event_handler( - "vertical_drag_start", self.__on_vertical_drag_start.get_handler() - ) - self.__on_vertical_drag_update = EventHandler(lambda e: DragUpdateEvent(e)) - self._add_event_handler( - "vertical_drag_update", self.__on_vertical_drag_update.get_handler() - ) - self.__on_vertical_drag_end = EventHandler(lambda e: DragEndEvent(e)) - self._add_event_handler( - "vertical_drag_end", self.__on_vertical_drag_end.get_handler() - ) - - # on_pan - - self.__on_pan_start = EventHandler(lambda e: DragStartEvent(e)) - self._add_event_handler("pan_start", self.__on_pan_start.get_handler()) - self.__on_pan_update = EventHandler(lambda e: DragUpdateEvent(e)) - self._add_event_handler("pan_update", self.__on_pan_update.get_handler()) - self.__on_pan_end = EventHandler(lambda e: DragEndEvent(e)) - self._add_event_handler("pan_end", self.__on_pan_end.get_handler()) - - # on_scale - - self.__on_scale_start = EventHandler(lambda e: ScaleStartEvent(e)) - self._add_event_handler("scale_start", self.__on_scale_start.get_handler()) - self.__on_scale_update = EventHandler(lambda e: ScaleUpdateEvent(e)) - self._add_event_handler("scale_update", self.__on_scale_update.get_handler()) - self.__on_scale_end = EventHandler(lambda e: ScaleEndEvent(e)) - self._add_event_handler("scale_end", self.__on_scale_end.get_handler()) - - # on_hover - - self.__on_hover = EventHandler(lambda e: HoverEvent(e)) - self._add_event_handler("hover", self.__on_hover.get_handler()) - self.__on_enter = EventHandler(lambda e: HoverEvent(e)) - self._add_event_handler("enter", self.__on_enter.get_handler()) - self.__on_exit = EventHandler(lambda e: HoverEvent(e)) - self._add_event_handler("exit", self.__on_exit.get_handler()) - - # on_scroll - self.__on_scroll = EventHandler(lambda e: ScrollEvent(e)) - self._add_event_handler("scroll", self.__on_scroll.get_handler()) - - self.content = content - self.mouse_cursor = mouse_cursor - self.drag_interval = drag_interval - self.hover_interval = hover_interval - self.on_tap = on_tap - self.on_tap_down = on_tap_down - self.on_tap_up = on_tap_up - self.on_multi_tap = on_multi_tap - self.multi_tap_touches = multi_tap_touches - self.on_multi_long_press = on_multi_long_press - self.on_secondary_tap = on_secondary_tap - self.on_secondary_tap_down = on_secondary_tap_down - self.on_secondary_tap_up = on_secondary_tap_up - self.on_long_press_start = on_long_press_start - self.on_long_press_end = on_long_press_end - self.on_secondary_long_press_start = on_secondary_long_press_start - self.on_secondary_long_press_end = on_secondary_long_press_end - self.on_double_tap = on_double_tap - self.on_double_tap_down = on_double_tap_down - self.on_horizontal_drag_start = on_horizontal_drag_start - self.on_horizontal_drag_update = on_horizontal_drag_update - self.on_horizontal_drag_end = on_horizontal_drag_end - self.on_vertical_drag_start = on_vertical_drag_start - self.on_vertical_drag_update = on_vertical_drag_update - self.on_vertical_drag_end = on_vertical_drag_end - self.on_pan_start = on_pan_start - self.on_pan_update = on_pan_update - self.on_pan_end = on_pan_end - self.on_scale_start = on_scale_start - self.on_scale_update = on_scale_update - self.on_scale_end = on_scale_end - self.on_hover = on_hover - self.on_enter = on_enter - self.on_exit = on_exit - self.on_scroll = on_scroll - self.exclude_from_semantics = exclude_from_semantics - self.trackpad_scroll_causes_scale = trackpad_scroll_causes_scale - self.allowed_devices = allowed_devices - - def _get_control_name(self): - return "gesturedetector" - - def before_update(self): - super().before_update() - self._set_attr_json("allowedDevices", self.__allowed_devices) - - def _get_children(self): - children = [] - if self.__content: - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - return children - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self.__mouse_cursor - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self.__mouse_cursor = value - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # drag_interval - @property - def drag_interval(self) -> Optional[int]: - return self._get_attr("dragInterval", data_type="int") - - @drag_interval.setter - def drag_interval(self, value: Optional[int]): - self._set_attr("dragInterval", value) - - # hover_interval - @property - def hover_interval(self) -> Optional[int]: - return self._get_attr("hoverInterval", data_type="int") - - @hover_interval.setter - def hover_interval(self, value: Optional[int]): - self._set_attr("hoverInterval", value) - - # exclude_from_semantics - @property - def exclude_from_semantics(self) -> Optional[bool]: - return self._get_attr("excludeFromSemantics", data_type="bool", def_value=False) - - @exclude_from_semantics.setter - def exclude_from_semantics(self, value: Optional[bool]): - self._set_attr("excludeFromSemantics", value) - - # trackpad_scroll_causes_scale - @property - def trackpad_scroll_causes_scale(self) -> Optional[bool]: - return self._get_attr( - "trackpadScrollCausesScale", data_type="bool", def_value=False - ) - - @trackpad_scroll_causes_scale.setter - def trackpad_scroll_causes_scale(self, value: Optional[bool]): - self._set_attr("trackpadScrollCausesScale", value) - - # allowed_devices - @property - def allowed_devices(self) -> Optional[Set[PointerDeviceType]]: - return self.__allowed_devices - - @allowed_devices.setter - def allowed_devices(self, value: Optional[Set[PointerDeviceType]]): - self.__allowed_devices = value - - # on_tap - @property - def on_tap(self) -> OptionalControlEventCallable: - return self._get_event_handler("tap") - - @on_tap.setter - def on_tap(self, handler: OptionalControlEventCallable): - self._add_event_handler("tap", handler) - self._set_attr("onTap", True if handler is not None else None) - - # on_tap_down - @property - def on_tap_down(self) -> OptionalEventCallable["TapEvent"]: - return self.__on_tap_down.handler - - @on_tap_down.setter - def on_tap_down(self, handler: OptionalEventCallable["TapEvent"]): - self.__on_tap_down.handler = handler - self._set_attr("onTapDown", True if handler is not None else None) - - # on_tap_up - @property - def on_tap_up(self) -> OptionalEventCallable["TapEvent"]: - return self.__on_tap_up.handler - - @on_tap_up.setter - def on_tap_up(self, handler: OptionalEventCallable["TapEvent"]): - self.__on_tap_up.handler = handler - self._set_attr("onTapUp", True if handler is not None else None) - - # on_multi_tap - @property - def on_multi_tap(self) -> OptionalEventCallable["MultiTapEvent"]: - return self.__on_multi_tap.handler - - @on_multi_tap.setter - def on_multi_tap(self, handler: OptionalEventCallable["MultiTapEvent"]): - self.__on_multi_tap.handler = handler - self._set_attr("onMultiTap", True if handler is not None else None) - - # multi_tap_touches - @property - def multi_tap_touches(self) -> Optional[int]: - return self._get_attr("multiTapTouches") - - @multi_tap_touches.setter - def multi_tap_touches(self, value: Optional[int]): - self._set_attr("multiTapTouches", value) - - # on_multi_long_press - @property - def on_multi_long_press(self) -> OptionalControlEventCallable: - return self._get_event_handler("multi_long_press") - - @on_multi_long_press.setter - def on_multi_long_press(self, handler: OptionalControlEventCallable): - self._add_event_handler("multi_long_press", handler) - self._set_attr("onMultiLongPress", True if handler is not None else None) - - # on_secondary_tap - @property - def on_secondary_tap(self) -> OptionalControlEventCallable: - return self._get_event_handler("secondary_tap") - - @on_secondary_tap.setter - def on_secondary_tap(self, handler: OptionalControlEventCallable): - self._add_event_handler("secondary_tap", handler) - self._set_attr("onSecondaryTap", True if handler is not None else None) - - # on_tap_down - @property - def on_secondary_tap_down(self) -> OptionalEventCallable["TapEvent"]: - return self.__on_secondary_tap_down.handler - - @on_secondary_tap_down.setter - def on_secondary_tap_down(self, handler: OptionalEventCallable["TapEvent"]): - self.__on_secondary_tap_down.handler = handler - self._set_attr("onSecondaryTapDown", True if handler is not None else None) - - # on_secondary_tap_up - @property - def on_secondary_tap_up(self) -> OptionalEventCallable["TapEvent"]: - return self.__on_secondary_tap_up.handler - - @on_secondary_tap_up.setter - def on_secondary_tap_up(self, handler: OptionalEventCallable["TapEvent"]): - self.__on_secondary_tap_up.handler = handler - self._set_attr("onSecondaryTapUp", True if handler is not None else None) - - # on_long_press_start - @property - def on_long_press_start(self) -> OptionalEventCallable["LongPressStartEvent"]: - return self.__on_long_press_start.handler - - @on_long_press_start.setter - def on_long_press_start( - self, handler: OptionalEventCallable["LongPressStartEvent"] - ): - self.__on_long_press_start.handler = handler - self._set_attr("onLongPressStart", True if handler is not None else None) - - # on_long_press_end - @property - def on_long_press_end(self) -> OptionalEventCallable["LongPressEndEvent"]: - return self.__on_long_press_end.handler - - @on_long_press_end.setter - def on_long_press_end(self, handler: OptionalEventCallable["LongPressEndEvent"]): - self.__on_long_press_end.handler = handler - self._set_attr("onLongPressEnd", True if handler is not None else None) - - # on_secondary_long_press_start - @property - def on_secondary_long_press_start( - self, - ) -> OptionalEventCallable["LongPressStartEvent"]: - return self.__on_secondary_long_press_start.handler - - @on_secondary_long_press_start.setter - def on_secondary_long_press_start( - self, handler: OptionalEventCallable["LongPressStartEvent"] - ): - self.__on_secondary_long_press_start.handler = handler - self._set_attr( - "onSecondaryLongPressStart", True if handler is not None else None - ) - - # on_secondary_long_press_end - @property - def on_secondary_long_press_end(self) -> OptionalEventCallable["LongPressEndEvent"]: - return self.__on_secondary_long_press_end.handler - - @on_secondary_long_press_end.setter - def on_secondary_long_press_end( - self, handler: OptionalEventCallable["LongPressEndEvent"] - ): - self.__on_secondary_long_press_end.handler = handler - self._set_attr("onSecondaryLongPressEnd", True if handler is not None else None) - - # on_double_tap - @property - def on_double_tap(self): - return self._get_event_handler("double_tap") - - @on_double_tap.setter - def on_double_tap(self, handler: OptionalEventCallable["TapEvent"]): - self._add_event_handler("double_tap", handler) - self._set_attr("onDoubleTap", True if handler is not None else None) - - # on_double_tap_down - @property - def on_double_tap_down(self) -> OptionalEventCallable["TapEvent"]: - return self.__on_double_tap_down.handler - - @on_double_tap_down.setter - def on_double_tap_down(self, handler: OptionalEventCallable["TapEvent"]): - self.__on_double_tap_down.handler = handler - self._set_attr("onDoubleTapDown", True if handler is not None else None) - - # on_horizontal_drag_start - @property - def on_horizontal_drag_start(self) -> OptionalEventCallable["DragStartEvent"]: - return self.__on_horizontal_drag_start.handler - - @on_horizontal_drag_start.setter - def on_horizontal_drag_start( - self, handler: OptionalEventCallable["DragStartEvent"] - ): - self.__on_horizontal_drag_start.handler = handler - self._set_attr("onHorizontalDragStart", True if handler is not None else None) - - # on_horizontal_drag_update - @property - def on_horizontal_drag_update(self) -> OptionalEventCallable["DragUpdateEvent"]: - return self.__on_horizontal_drag_update.handler - - @on_horizontal_drag_update.setter - def on_horizontal_drag_update( - self, handler: OptionalEventCallable["DragUpdateEvent"] - ): - self.__on_horizontal_drag_update.handler = handler - self._set_attr("onHorizontalDragUpdate", True if handler is not None else None) - - # on_horizontal_drag_end - @property - def on_horizontal_drag_end(self) -> OptionalEventCallable["DragEndEvent"]: - return self.__on_horizontal_drag_end.handler - - @on_horizontal_drag_end.setter - def on_horizontal_drag_end(self, handler: OptionalEventCallable["DragEndEvent"]): - self.__on_horizontal_drag_end.handler = handler - self._set_attr("onHorizontalDragEnd", True if handler is not None else None) - - # on_vertical_drag_start - @property - def on_vertical_drag_start(self) -> OptionalEventCallable["DragStartEvent"]: - return self.__on_vertical_drag_start.handler - - @on_vertical_drag_start.setter - def on_vertical_drag_start(self, handler: OptionalEventCallable["DragStartEvent"]): - self.__on_vertical_drag_start.handler = handler - self._set_attr("onVerticalDragStart", True if handler is not None else None) - - # on_vertical_drag_update - @property - def on_vertical_drag_update(self) -> OptionalEventCallable["DragUpdateEvent"]: - return self.__on_vertical_drag_update.handler - - @on_vertical_drag_update.setter - def on_vertical_drag_update( - self, handler: OptionalEventCallable["DragUpdateEvent"] - ): - self.__on_vertical_drag_update.handler = handler - self._set_attr("onVerticalDragUpdate", True if handler is not None else None) - - # on_vertical_drag_end - @property - def on_vertical_drag_end(self) -> OptionalEventCallable["DragEndEvent"]: - return self.__on_vertical_drag_end.handler - - @on_vertical_drag_end.setter - def on_vertical_drag_end(self, handler: OptionalEventCallable["DragEndEvent"]): - self.__on_vertical_drag_end.handler = handler - self._set_attr("onVerticalDragEnd", True if handler is not None else None) - - # on_pan_start - @property - def on_pan_start(self) -> OptionalEventCallable["DragStartEvent"]: - return self.__on_pan_start.handler - - @on_pan_start.setter - def on_pan_start(self, handler: OptionalEventCallable["DragStartEvent"]): - self.__on_pan_start.handler = handler - self._set_attr("onPanStart", True if handler is not None else None) - - # on_pan_updatevertical_drag - @property - def on_pan_update(self) -> OptionalEventCallable["DragUpdateEvent"]: - return self.__on_pan_update.handler - - @on_pan_update.setter - def on_pan_update(self, handler: OptionalEventCallable["DragUpdateEvent"]): - self.__on_pan_update.handler = handler - self._set_attr("onPanUpdate", True if handler is not None else None) - - # on_pan_end - @property - def on_pan_end(self) -> OptionalEventCallable["DragEndEvent"]: - return self.__on_pan_end.handler - - @on_pan_end.setter - def on_pan_end(self, handler: OptionalEventCallable["DragEndEvent"]): - self.__on_pan_end.handler = handler - self._set_attr("onPanEnd", True if handler is not None else None) - - # on_scale_start - @property - def on_scale_start(self) -> OptionalEventCallable["ScaleStartEvent"]: - return self.__on_scale_start.handler - - @on_scale_start.setter - def on_scale_start(self, handler: OptionalEventCallable["ScaleStartEvent"]): - self.__on_scale_start.handler = handler - self._set_attr("onScaleStart", True if handler is not None else None) - - # on_scale_update - @property - def on_scale_update(self) -> OptionalEventCallable["ScaleUpdateEvent"]: - return self.__on_scale_update.handler - - @on_scale_update.setter - def on_scale_update(self, handler: OptionalEventCallable["ScaleUpdateEvent"]): - self.__on_scale_update.handler = handler - self._set_attr("onScaleUpdate", True if handler is not None else None) - - # on_scale_end - @property - def on_scale_end(self) -> OptionalEventCallable["ScaleEndEvent"]: - return self.__on_scale_end.handler - - @on_scale_end.setter - def on_scale_end(self, handler: OptionalEventCallable["ScaleEndEvent"]): - self.__on_scale_end.handler = handler - self._set_attr("onScaleEnd", True if handler is not None else None) - - # on_hover - @property - def on_hover(self) -> OptionalEventCallable["HoverEvent"]: - return self.__on_hover.handler - - @on_hover.setter - def on_hover(self, handler: OptionalEventCallable["HoverEvent"]): - self.__on_hover.handler = handler - self._set_attr("onHover", True if handler is not None else None) - - # on_enter - @property - def on_enter(self) -> OptionalEventCallable["HoverEvent"]: - return self.__on_enter.handler - - @on_enter.setter - def on_enter(self, handler: OptionalEventCallable["HoverEvent"]): - self.__on_enter.handler = handler - self._set_attr("onEnter", True if handler is not None else None) - - # on_exit - @property - def on_exit(self) -> OptionalEventCallable["HoverEvent"]: - return self.__on_exit.handler - - @on_exit.setter - def on_exit(self, handler: OptionalEventCallable["HoverEvent"]): - self.__on_exit.handler = handler - self._set_attr("onExit", True if handler is not None else None) - - # on_scroll - @property - def on_scroll(self) -> OptionalEventCallable["ScrollEvent"]: - return self.__on_scroll.handler - - @on_scroll.setter - def on_scroll(self, handler: OptionalEventCallable["ScrollEvent"]): - self.__on_scroll.handler = handler - self._set_attr("onScroll", True if handler is not None else None) - - -class TapEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.local_x: float = d.get("lx") - self.local_y: float = d.get("ly") - self.global_x: float = d.get("gx") - self.global_y: float = d.get("gy") - self.kind: str = d.get("kind") - - -class MultiTapEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - self.correct_touches: bool = e.data.lower() == "true" - - -class LongPressStartEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.local_x: float = d.get("lx") - self.local_y: float = d.get("ly") - self.global_x: float = d.get("gx") - self.global_y: float = d.get("gy") - - -class LongPressEndEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.local_x: float = d.get("lx") - self.local_y: float = d.get("ly") - self.global_x: float = d.get("gx") - self.global_y: float = d.get("gy") - self.velocity_x: float = d.get("vx") - self.velocity_y: float = d.get("vy") - - -class DragStartEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.kind: str = d.get("kind") - self.local_x: float = d.get("lx") - self.local_y: float = d.get("ly") - self.global_x: float = d.get("gx") - self.global_y: float = d.get("gy") - self.timestamp: Optional[int] = d.get("ts") - - -class DragUpdateEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.delta_x: float = d.get("dx") - self.delta_y: float = d.get("dy") - self.primary_delta: Optional[float] = d.get("pd") - self.local_x: float = d.get("lx") - self.local_y: float = d.get("ly") - self.global_x: float = d.get("gx") - self.global_y: float = d.get("gy") - self.timestamp: Optional[int] = d.get("ts") - - -class DragEndEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.primary_velocity: Optional[float] = d.get("pv") - self.velocity_x: float = d.get("vx") - self.velocity_y: float = d.get("vy") - - -class ScaleStartEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.focal_point_x: float = d.get("fpx") - self.focal_point_y: float = d.get("fpy") - self.local_focal_point_x: float = d.get("lfpx") - self.local_focal_point_y: float = d.get("lfpy") - self.pointer_count: int = d.get("pc") - - -class ScaleUpdateEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.focal_point_x: float = d.get("fpx") - self.focal_point_y: float = d.get("fpy") - self.focal_point_delta_x: float = d.get("fpdx") - self.focal_point_delta_y: float = d.get("fpdy") - self.local_focal_point_x: float = d.get("lfpx") - self.local_focal_point_y: float = d.get("lfpy") - self.pointer_count: int = d.get("pc") - self.horizontal_scale: float = d.get("hs") - self.vertical_scale: float = d.get("vs") - self.scale: float = d.get("s") - self.rotation: float = d.get("r") - - -class ScaleEndEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.pointer_count: int = d.get("pc") - self.velocity_x: float = d.get("vx") - self.velocity_y: float = d.get("vy") - - -class HoverEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.timestamp: float = d.get("ts") - self.kind: str = d.get("kind") - self.global_x: float = d.get("gx") - self.global_y: float = d.get("gy") - self.local_x: float = d.get("lx") - self.local_y: float = d.get("ly") - self.delta_x: Optional[float] = d.get("dx") - self.delta_y: Optional[float] = d.get("dy") - - -class ScrollEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.global_x: float = d.get("gx") - self.global_y: float = d.get("gy") - self.local_x: float = d.get("lx") - self.local_y: float = d.get("ly") - self.scroll_delta_x: Optional[float] = d.get("dx") - self.scroll_delta_y: Optional[float] = d.get("dy") diff --git a/sdk/python/packages/flet/src/flet/core/gradients.py b/sdk/python/packages/flet/src/flet/core/gradients.py deleted file mode 100644 index 3671f241d..000000000 --- a/sdk/python/packages/flet/src/flet/core/gradients.py +++ /dev/null @@ -1,47 +0,0 @@ -import dataclasses -import math -from dataclasses import field -from enum import Enum -from typing import List, Optional, Union - -from flet.core import alignment -from flet.core.alignment import Alignment - - -class GradientTileMode(Enum): - CLAMP = "clamp" - DECAL = "decal" - MIRROR = "mirror" - REPEATED = "repeated" - - -@dataclasses.dataclass -class Gradient: - colors: List[str] - tile_mode: GradientTileMode = field(default=GradientTileMode.CLAMP) - rotation: Union[None, float, int] = field(default=None) - stops: Optional[List[float]] = field(default=None) - - -@dataclasses.dataclass -class LinearGradient(Gradient): - begin: Alignment = field(default_factory=lambda: alignment.center_left) - end: Alignment = field(default_factory=lambda: alignment.center_right) - type: str = field(default="linear") - - -@dataclasses.dataclass -class RadialGradient(Gradient): - center: Alignment = field(default_factory=lambda: alignment.center) - radius: Union[float, int] = field(default=0.5) - focal: Optional[Alignment] = field(default=None) - focal_radius: Union[float, int] = field(default=0.0) - type: str = field(default="radial") - - -@dataclasses.dataclass -class SweepGradient(Gradient): - center: Alignment = field(default_factory=lambda: alignment.center) - start_angle: Union[float, int] = field(default=0.0) - end_angle: Union[float, int] = field(default=math.pi * 2) - type: str = field(default="sweep") diff --git a/sdk/python/packages/flet/src/flet/core/grid_view.py b/sdk/python/packages/flet/src/flet/core/grid_view.py deleted file mode 100644 index ec69b9711..000000000 --- a/sdk/python/packages/flet/src/flet/core/grid_view.py +++ /dev/null @@ -1,300 +0,0 @@ -from typing import Any, Callable, List, Optional, Sequence, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.scrollable_control import OnScrollEvent, ScrollableControl -from flet.core.types import ( - ClipBehavior, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class GridView(ConstrainedControl, ScrollableControl, AdaptiveControl): - """ - A scrollable, 2D array of controls. - - GridView is very effective for large lists (thousands of items). Prefer it over wrapping `Column` or `Row` for smooth scrolling. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.title = "GridView Example" - page.theme_mode = ft.ThemeMode.DARK - page.padding = 50 - page.update() - - images = ft.GridView( - expand=1, - runs_count=5, - max_extent=150, - child_aspect_ratio=1.0, - spacing=5, - run_spacing=5, - ) - - page.add(images) - - for i in range(0, 60): - images.controls.append( - ft.Image( - src=f"https://picsum.photos/150/150?{i}", - fit=ft.ImageFit.NONE, - repeat=ft.ImageRepeat.NO_REPEAT, - border_radius=ft.border_radius.all(10), - ) - ) - page.update() - - ft.app(target=main) - - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/gridview - """ - - def __init__( - self, - controls: Optional[Sequence[Control]] = None, - horizontal: Optional[bool] = None, - runs_count: Optional[int] = None, - max_extent: Optional[int] = None, - spacing: OptionalNumber = None, - run_spacing: OptionalNumber = None, - child_aspect_ratio: OptionalNumber = None, - padding: Optional[PaddingValue] = None, - clip_behavior: Optional[ClipBehavior] = None, - semantic_child_count: Optional[int] = None, - build_controls_on_demand: Optional[bool] = None, - cache_extent: OptionalNumber = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - # - # ScrollableControl - # - auto_scroll: Optional[bool] = None, - reverse: Optional[bool] = None, - on_scroll_interval: OptionalNumber = None, - on_scroll: Optional[Callable[[OnScrollEvent], None]] = None, - # - # AdaptiveControl - # - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - ) - - ScrollableControl.__init__( - self, - auto_scroll=auto_scroll, - reverse=reverse, - on_scroll_interval=on_scroll_interval, - on_scroll=on_scroll, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.__controls: List[Control] = [] - self.controls = controls - self.horizontal = horizontal - self.runs_count = runs_count - self.max_extent = max_extent - self.spacing = spacing - self.run_spacing = run_spacing - self.child_aspect_ratio = child_aspect_ratio - self.padding = padding - self.clip_behavior = clip_behavior - self.semantic_child_count = semantic_child_count - self.cache_extent = cache_extent - self.build_controls_on_demand = build_controls_on_demand - - def _get_control_name(self): - return "gridview" - - def __contains__(self, item): - return item in self.__controls - - def before_update(self): - super().before_update() - self._set_attr_json("padding", self.__padding) - - def _get_children(self): - return self.__controls - - def clean(self): - super().clean() - self.__controls.clear() - - # build_controls_on_demand - @property - def build_controls_on_demand(self) -> Optional[bool]: - return self._get_attr("buildControlsOnDemand", data_type="bool", def_value=True) - - @build_controls_on_demand.setter - def build_controls_on_demand(self, value: Optional[bool]): - self._set_attr("buildControlsOnDemand", value) - - # horizontal - @property - def horizontal(self) -> bool: - return self._get_attr("horizontal", data_type="bool", def_value=False) - - @horizontal.setter - def horizontal(self, value: Optional[bool]): - self._set_attr("horizontal", value) - - # cache_extent - @property - def cache_extent(self) -> OptionalNumber: - return self._get_attr("cacheExtent") - - @cache_extent.setter - def cache_extent(self, value: OptionalNumber): - self._set_attr("cacheExtent", value) - - # runs_count - @property - def runs_count(self) -> Optional[int]: - return self._get_attr("runsCount") - - @runs_count.setter - def runs_count(self, value: Optional[int]): - self._set_attr("runsCount", value) - - # max_extent - @property - def max_extent(self) -> OptionalNumber: - return self._get_attr("maxExtent") - - @max_extent.setter - def max_extent(self, value: OptionalNumber): - self._set_attr("maxExtent", value) - - # spacing - @property - def spacing(self) -> OptionalNumber: - return self._get_attr("spacing") - - @spacing.setter - def spacing(self, value: OptionalNumber): - self._set_attr("spacing", value) - - # run_spacing - @property - def run_spacing(self) -> OptionalNumber: - return self._get_attr("runSpacing") - - @run_spacing.setter - def run_spacing(self, value: OptionalNumber): - self._set_attr("runSpacing", value) - - # child_aspect_ratio - @property - def child_aspect_ratio(self) -> OptionalNumber: - return self._get_attr("childAspectRatio") - - @child_aspect_ratio.setter - def child_aspect_ratio(self, value: OptionalNumber): - self._set_attr("childAspectRatio", value) - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # controls - @property - def controls(self): - return self.__controls - - @controls.setter - def controls(self, value: Optional[Sequence[Control]]): - self.__controls = list(value) if value is not None else [] - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # semantic_child_count - @property - def semantic_child_count(self) -> Optional[int]: - return self._get_attr("semanticChildCount", data_type="int") - - @semantic_child_count.setter - def semantic_child_count(self, value: Optional[int]): - self._set_attr("semanticChildCount", value) diff --git a/sdk/python/packages/flet/src/flet/core/haptic_feedback.py b/sdk/python/packages/flet/src/flet/core/haptic_feedback.py deleted file mode 100644 index 245ea7872..000000000 --- a/sdk/python/packages/flet/src/flet/core/haptic_feedback.py +++ /dev/null @@ -1,63 +0,0 @@ -from typing import Any, Optional - -from flet.core.control import Control -from flet.core.ref import Ref - - -class HapticFeedback(Control): - """ - Allows access to the haptic feedback interface on the device. - - It is non-visual and should be added to `page.overlay` list. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - hf = ft.HapticFeedback() - page.overlay.append(hf) - - page.add( - ft.ElevatedButton("Heavy impact", on_click=lambda _: hf.heavy_impact()), - ft.ElevatedButton("Medium impact", on_click=lambda _: hf.medium_impact()), - ft.ElevatedButton("Light impact", on_click=lambda _: hf.light_impact()), - ft.ElevatedButton("Vibrate", on_click=lambda _: hf.vibrate()), - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/hapticfeedback - """ - - def __init__( - self, - ref: Optional[Ref] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - data=data, - ) - - def _get_control_name(self): - return "hapticfeedback" - - def is_isolated(self): - return True - - def heavy_impact(self): - self.invoke_method("heavy_impact") - - def light_impact(self): - self.invoke_method("light_impact") - - def medium_impact(self): - self.invoke_method("medium_impact") - - def vibrate(self): - self.invoke_method("vibrate") diff --git a/sdk/python/packages/flet/src/flet/core/icon.py b/sdk/python/packages/flet/src/flet/core/icon.py deleted file mode 100644 index 1a1e04fd4..000000000 --- a/sdk/python/packages/flet/src/flet/core/icon.py +++ /dev/null @@ -1,239 +0,0 @@ -from typing import Any, List, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.box import BoxShadow -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - BlendMode, - ColorEnums, - ColorValue, - IconEnums, - IconValue, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class Icon(ConstrainedControl): - """ - Displays a Material icon. - - Icon browser: https://flet-icons-browser.fly.dev/#/ - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.add( - ft.Row( - [ - ft.Icon(name=ft.icons.FAVORITE, color=ft.colors.PINK), - ft.Icon(name=ft.icons.AUDIOTRACK, color=ft.colors.GREEN_400, size=30), - ft.Icon(name=ft.icons.BEACH_ACCESS, color=ft.colors.BLUE, size=50), - ft.Icon(name="settings", color="#c1c1c1"), - ] - ) - ) - - ft.app(target=main) - - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/icon - """ - - def __init__( - self, - name: Optional[IconValue] = None, - color: Optional[ColorValue] = None, - size: OptionalNumber = None, - semantics_label: Optional[str] = None, - shadows: Union[BoxShadow, List[BoxShadow], None] = None, - fill: OptionalNumber = None, - apply_text_scaling: Optional[bool] = None, - grade: OptionalNumber = None, - weight: OptionalNumber = None, - optical_size: OptionalNumber = None, - blend_mode: Optional[BlendMode] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.name = name - self.color = color - self.size = size - self.semantics_label = semantics_label - self.shadows = shadows - self.fill = fill - self.apply_text_scaling = apply_text_scaling - self.grade = grade - self.weight = weight - self.optical_size = optical_size - self.blend_mode = blend_mode - - def _get_control_name(self): - return "icon" - - def before_update(self): - super().before_update() - self._set_attr_json("shadows", self.__shadows) - - # name - @property - def name(self) -> Optional[IconValue]: - return self.__name - - @name.setter - def name(self, value: Optional[IconValue]): - self.__name = value - self._set_enum_attr("name", value, IconEnums) - - # blend_mode - @property - def blend_mode(self) -> Optional[BlendMode]: - return self.__blend_mode - - @blend_mode.setter - def blend_mode(self, value: Optional[BlendMode]): - self.__blend_mode = value - self._set_enum_attr("blendMode", value, BlendMode) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # size - @property - def size(self) -> OptionalNumber: - return self._get_attr("size", data_type="float") - - @size.setter - def size(self, value: OptionalNumber): - self._set_attr("size", value) - - # semantics_label - @property - def semantics_label(self) -> Optional[str]: - return self._get_attr("semanticsLabel") - - @semantics_label.setter - def semantics_label(self, value: Optional[str]): - self._set_attr("semanticsLabel", value) - - # shadows - @property - def shadows(self) -> Union[BoxShadow, List[BoxShadow], None]: - return self.__shadows - - @shadows.setter - def shadows(self, value: Union[BoxShadow, List[BoxShadow], None]): - self.__shadows = value - - # fill - @property - def fill(self) -> OptionalNumber: - return self._get_attr("fill", data_type="float") - - @fill.setter - def fill(self, value: OptionalNumber): - self._set_attr("fill", value) - - # apply_text_scaling - @property - def apply_text_scaling(self) -> Optional[bool]: - return self._get_attr("applyTextScaling", data_type="bool") - - @apply_text_scaling.setter - def apply_text_scaling(self, value: Optional[bool]): - self._set_attr("applyTextScaling", value) - - # grade - @property - def grade(self) -> OptionalNumber: - return self._get_attr("grade", data_type="float") - - @grade.setter - def grade(self, value: OptionalNumber): - self._set_attr("grade", value) - - # weight - @property - def weight(self) -> OptionalNumber: - return self._get_attr("weight", data_type="float") - - @weight.setter - def weight(self, value: OptionalNumber): - self._set_attr("weight", value) - - # optical_size - @property - def optical_size(self) -> OptionalNumber: - return self._get_attr("opticalSize", data_type="float") - - @optical_size.setter - def optical_size(self, value: OptionalNumber): - self._set_attr("opticalSize", value) diff --git a/sdk/python/packages/flet/src/flet/core/icon_button.py b/sdk/python/packages/flet/src/flet/core/icon_button.py deleted file mode 100644 index eeaa52fa8..000000000 --- a/sdk/python/packages/flet/src/flet/core/icon_button.py +++ /dev/null @@ -1,484 +0,0 @@ -import time -from typing import Any, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.alignment import Alignment -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.box import BoxConstraints -from flet.core.buttons import ButtonStyle -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - IconEnums, - IconValue, - MouseCursor, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, - UrlTarget, - VisualDensity, -) - - -class IconButton(ConstrainedControl, AdaptiveControl): - """ - An icon button is a round button with an icon in the middle that reacts to touches by filling with color (ink). - - Icon buttons are commonly used in the toolbars, but they can be used in many other places as well. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.title = "Icon buttons" - page.add( - ft.Row( - [ - ft.IconButton( - icon=ft.icons.PAUSE_CIRCLE_FILLED_ROUNDED, - icon_color="blue400", - icon_size=20, - tooltip="Pause record", - ), - ft.IconButton( - icon=ft.icons.DELETE_FOREVER_ROUNDED, - icon_color="pink600", - icon_size=40, - tooltip="Delete record", - ), - ] - ), - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/iconbutton - """ - - def __init__( - self, - icon: Optional[IconValue] = None, - icon_color: Optional[ColorValue] = None, - icon_size: OptionalNumber = None, - selected: Optional[bool] = None, - selected_icon: Optional[IconValue] = None, - selected_icon_color: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, - highlight_color: Optional[ColorValue] = None, - style: Optional[ButtonStyle] = None, - content: Optional[Control] = None, - autofocus: Optional[bool] = None, - disabled_color: Optional[ColorValue] = None, - hover_color: Optional[ColorValue] = None, - focus_color: Optional[ColorValue] = None, - splash_color: Optional[ColorValue] = None, - splash_radius: OptionalNumber = None, - alignment: Optional[Alignment] = None, - padding: Optional[PaddingValue] = None, - enable_feedback: Optional[bool] = None, - url: Optional[str] = None, - url_target: Optional[UrlTarget] = None, - mouse_cursor: Optional[MouseCursor] = None, - visual_density: Optional[VisualDensity] = None, - size_constraints: Optional[BoxConstraints] = None, - on_click: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - on_long_press: OptionalControlEventCallable = None, - on_hover: OptionalControlEventCallable = None, - # - # ConstrainedControl and AdaptiveControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.icon = icon - self.icon_size = icon_size - self.icon_color = icon_color - self.highlight_color = highlight_color - self.selected_icon = selected_icon - self.selected_icon_color = selected_icon_color - self.selected = selected - self.bgcolor = bgcolor - self.style = style - self.content = content - self.autofocus = autofocus - self.disabled_color = disabled_color - self.hover_color = hover_color - self.alignment = alignment - self.padding = padding - self.enable_feedback = enable_feedback - self.splash_color = splash_color - self.splash_radius = splash_radius - self.focus_color = focus_color - self.url = url - self.url_target = url_target - self.on_click = on_click - self.on_focus = on_focus - self.on_blur = on_blur - self.mouse_cursor = mouse_cursor - self.visual_density = visual_density - self.size_constraints = size_constraints - self.on_long_press = on_long_press - self.on_hover = on_hover - - def _get_control_name(self): - return "iconbutton" - - def before_update(self): - super().before_update() - if self.__style is not None: - self.__style.side = self._wrap_attr_dict(self.__style.side) - self.__style.shape = self._wrap_attr_dict(self.__style.shape) - self.__style.padding = self._wrap_attr_dict(self.__style.padding) - self._set_attr_json("style", self.__style) - self._set_attr_json("alignment", self.__alignment) - self._set_attr_json("padding", self.__padding) - self._set_attr_json("sizeConstraints", self.__size_constraints) - - def _get_children(self): - if self.__content is None: - return [] - self.__content._set_attr_internal("n", "content") - return [self.__content] - - def focus(self): - self._set_attr_json("focus", str(time.time())) - self.update() - - # icon - @property - def icon(self) -> Optional[IconValue]: - return self.__icon - - @icon.setter - def icon(self, value: Optional[IconValue]): - self.__icon = value - self._set_enum_attr("icon", value, IconEnums) - - # selected_icon - @property - def selected_icon(self) -> Optional[IconValue]: - return self.__selected_icon - - @selected_icon.setter - def selected_icon(self, value: Optional[IconValue]): - self.__selected_icon = value - self._set_enum_attr("selectedIcon", value, IconEnums) - - # icon_size - @property - def icon_size(self) -> OptionalNumber: - return self._get_attr("iconSize", data_type="float") - - @icon_size.setter - def icon_size(self, value: OptionalNumber): - self._set_attr("iconSize", value) - - # splash_radius - @property - def splash_radius(self) -> OptionalNumber: - return self._get_attr("splashRadius", data_type="float") - - @splash_radius.setter - def splash_radius(self, value: OptionalNumber): - self._set_attr("splashRadius", value) - - # splash_color - @property - def splash_color(self) -> Optional[ColorValue]: - return self.__splash_color - - @splash_color.setter - def splash_color(self, value: Optional[ColorValue]): - self.__splash_color = value - self._set_enum_attr("splashColor", value, ColorEnums) - - # icon_color - @property - def icon_color(self) -> Optional[ColorValue]: - return self.__icon_color - - @icon_color.setter - def icon_color(self, value: Optional[ColorValue]): - self.__icon_color = value - self._set_enum_attr("iconColor", value, ColorEnums) - - # highlight_color - @property - def highlight_color(self) -> Optional[ColorValue]: - return self.__highlight_color - - @highlight_color.setter - def highlight_color(self, value: Optional[ColorValue]): - self.__highlight_color = value - self._set_enum_attr("highlightColor", value, ColorEnums) - - # selected_icon_color - @property - def selected_icon_color(self) -> Optional[ColorValue]: - return self.__selected_icon_color - - @selected_icon_color.setter - def selected_icon_color(self, value: Optional[ColorValue]): - self.__selected_icon_color = value - self._set_enum_attr("selectedIconColor", value, ColorEnums) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # hover_color - @property - def hover_color(self) -> Optional[ColorValue]: - return self.__hover_color - - @hover_color.setter - def hover_color(self, value: Optional[ColorValue]): - self.__hover_color = value - self._set_enum_attr("hoverColor", value, ColorEnums) - - # focus_color - @property - def focus_color(self) -> Optional[ColorValue]: - return self.__focus_color - - @focus_color.setter - def focus_color(self, value: Optional[ColorValue]): - self.__focus_color = value - self._set_enum_attr("focusColor", value, ColorEnums) - - # disabled_color - @property - def disabled_color(self) -> Optional[ColorValue]: - return self.__disabled_color - - @disabled_color.setter - def disabled_color(self, value: Optional[ColorValue]): - self.__disabled_color = value - self._set_enum_attr("disabledColor", value, ColorEnums) - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # size_constraints - @property - def size_constraints(self) -> Optional[BoxConstraints]: - return self.__size_constraints - - @size_constraints.setter - def size_constraints(self, value: Optional[BoxConstraints]): - self.__size_constraints = value - - # selected - @property - def selected(self) -> bool: - return self._get_attr("selected", data_type="bool", def_value=False) - - @selected.setter - def selected(self, value: Optional[bool]): - self._set_attr("selected", value) - - # enable_feedback - @property - def enable_feedback(self) -> bool: - return self._get_attr("enableFeedback", data_type="bool", def_value=True) - - @enable_feedback.setter - def enable_feedback(self, value: Optional[bool]): - self._set_attr("enableFeedback", value) - - # style - @property - def style(self) -> Optional[ButtonStyle]: - return self.__style - - @style.setter - def style(self, value: Optional[ButtonStyle]): - self.__style = value - - # url - @property - def url(self) -> Optional[str]: - return self._get_attr("url") - - @url.setter - def url(self, value: Optional[str]): - self._set_attr("url", value) - - # url_target - @property - def url_target(self) -> Optional[UrlTarget]: - return self.__url_target - - @url_target.setter - def url_target(self, value: Optional[UrlTarget]): - self.__url_target = value - self._set_enum_attr("urlTarget", value, UrlTarget) - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self.__mouse_cursor - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self.__mouse_cursor = value - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # visual_density - @property - def visual_density(self) -> Optional[VisualDensity]: - return self.__visual_density - - @visual_density.setter - def visual_density(self, value: Optional[VisualDensity]): - self.__visual_density = value - self._set_enum_attr("visualDensity", value, VisualDensity) - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) - - # on_hover - @property - def on_hover(self) -> OptionalControlEventCallable: - return self._get_event_handler("hover") - - @on_hover.setter - def on_hover(self, handler: OptionalControlEventCallable): - self._add_event_handler("hover", handler) - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value diff --git a/sdk/python/packages/flet/src/flet/core/image.py b/sdk/python/packages/flet/src/flet/core/image.py deleted file mode 100644 index c277610d7..000000000 --- a/sdk/python/packages/flet/src/flet/core/image.py +++ /dev/null @@ -1,308 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.box import FilterQuality -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - BlendMode, - BorderRadiusValue, - ColorEnums, - ColorValue, - ImageFit, - ImageRepeat, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal - - -class Image(ConstrainedControl): - """ - A control that displays an image. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.title = "Image Example" - - img = ft.Image( - src=f"/icons/icon-512.png", - width=100, - height=100, - fit=ft.ImageFit.CONTAIN, - ) - - page.add(img) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/image - """ - - def __init__( - self, - src: Optional[str] = None, - src_base64: Optional[str] = None, - error_content: Optional[Control] = None, - repeat: Optional[ImageRepeat] = None, - fit: Optional[ImageFit] = None, - border_radius: Optional[BorderRadiusValue] = None, - color: Optional[ColorValue] = None, - color_blend_mode: Optional[BlendMode] = None, - gapless_playback: Optional[bool] = None, - semantics_label: Optional[str] = None, - exclude_from_semantics: Optional[bool] = None, - filter_quality: Optional[FilterQuality] = None, - cache_width: Optional[int] = None, - cache_height: Optional[int] = None, - anti_alias: Optional[bool] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.src = src - self.src_base64 = src_base64 - self.error_content = error_content - self.fit = fit - self.repeat = repeat - self.border_radius = border_radius - self.color = color - self.color_blend_mode = color_blend_mode - self.gapless_playback = gapless_playback - self.semantics_label = semantics_label - self.exclude_from_semantics = exclude_from_semantics - self.filter_quality = filter_quality - self.cache_width = cache_width - self.cache_height = cache_height - self.anti_alias = anti_alias - - def _get_control_name(self): - return "image" - - def _get_children(self): - if self.__error_content is not None: - self.__error_content._set_attr_internal("n", "error_content") - return [self.__error_content] - return [] - - def before_update(self): - super().before_update() - self._set_attr_json("borderRadius", self.__border_radius) - - # src - @property - def src(self): - return self._get_attr("src") - - @src.setter - def src(self, value: Optional[str]): - self._set_attr("src", value) - - # src_base64 - @property - def src_base64(self): - return self._get_attr("srcBase64") - - @src_base64.setter - def src_base64(self, value: Optional[str]): - self._set_attr("srcBase64", value) - - # fit - @property - def fit(self) -> Optional[ImageFit]: - return self.__fit - - @fit.setter - def fit(self, value: Optional[ImageFit]): - self.__fit = value - self._set_attr("fit", value.value if isinstance(value, ImageFit) else value) - - # filter_quality - @property - def filter_quality(self) -> Optional[FilterQuality]: - return self.__filter_quality - - @filter_quality.setter - def filter_quality(self, value: Optional[FilterQuality]): - self.__filter_quality = value - self._set_attr( - "filterQuality", value.value if isinstance(value, FilterQuality) else value - ) - - # repeat - @property - def repeat(self) -> Optional[ImageRepeat]: - return self.__repeat - - @repeat.setter - def repeat(self, value: Optional[ImageRepeat]): - self.__repeat = value - self._set_enum_attr("repeat", value, ImageRepeat) - - # cache_width - @property - def cache_width(self) -> Optional[int]: - return self._get_attr("cacheWidth", data_type="int") - - @cache_width.setter - def cache_width(self, value: Optional[int]): - self._set_attr("cacheWidth", value) - - # cache_height - @property - def cache_height(self) -> Optional[int]: - return self._get_attr("cacheHeight", data_type="int") - - @cache_height.setter - def cache_height(self, value: Optional[int]): - self._set_attr("cacheHeight", value) - - # anti_alias - @property - def anti_alias(self) -> Optional[bool]: - return self._get_attr("antiAlias", data_type="bool", def_value=False) - - @anti_alias.setter - def anti_alias(self, value: Optional[bool]): - self._set_attr("antiAlias", value) - - # border_radius - @property - def border_radius(self) -> Optional[BorderRadiusValue]: - return self.__border_radius - - @border_radius.setter - def border_radius(self, value: Optional[BorderRadiusValue]): - self.__border_radius = value - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # color_blend_mode - @property - def color_blend_mode(self) -> Optional[BlendMode]: - return self.__blend_mode - - @color_blend_mode.setter - def color_blend_mode(self, value: Optional[BlendMode]): - self.__blend_mode = value - self._set_enum_attr("colorBlendMode", value, BlendMode) - - # gapless_playback - @property - def gapless_playback(self) -> bool: - return self._get_attr("gaplessPlayback", data_type="bool", def_value=False) - - @gapless_playback.setter - def gapless_playback(self, value: Optional[bool]): - self._set_attr("gaplessPlayback", value) - - # exclude_from_semantics - @property - def exclude_from_semantics(self) -> bool: - return self._get_attr("excludeFromSemantics", data_type="bool", def_value=False) - - @exclude_from_semantics.setter - def exclude_from_semantics(self, value: Optional[bool]): - self._set_attr("excludeFromSemantics", value) - - # semantics_label - @property - def semantics_label(self) -> Optional[str]: - return self._get_attr("semanticsLabel") - - @semantics_label.setter - def semantics_label(self, value: Optional[str]): - self._set_attr("semanticsLabel", value) - - # error_content - @property - def error_content(self) -> Optional[Control]: - return self.__error_content - - @error_content.setter - def error_content(self, value: Optional[Control]): - self.__error_content = value diff --git a/sdk/python/packages/flet/src/flet/core/inline_span.py b/sdk/python/packages/flet/src/flet/core/inline_span.py deleted file mode 100644 index 59c74fc84..000000000 --- a/sdk/python/packages/flet/src/flet/core/inline_span.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Any, Optional - -from flet.core.control import Control - - -class InlineSpan(Control): - def __init__( - self, - # base - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Control.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data) diff --git a/sdk/python/packages/flet/src/flet/core/interactive_viewer.py b/sdk/python/packages/flet/src/flet/core/interactive_viewer.py deleted file mode 100644 index 652473da5..000000000 --- a/sdk/python/packages/flet/src/flet/core/interactive_viewer.py +++ /dev/null @@ -1,395 +0,0 @@ -import json -from typing import Any, Callable, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.alignment import Alignment -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ClipBehavior, - DurationValue, - MarginValue, - Number, - Offset, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class InteractiveViewerInteractionStartEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.pointer_count: int = d.get("pc") - self.global_focal_point: Offset = Offset(d.get("fp_x"), d.get("fp_y")) - self.local_focal_point: Offset = Offset(d.get("lfp_x"), d.get("lfp_y")) - - -class InteractiveViewerInteractionUpdateEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.pointer_count: int = d.get("pc") - self.global_focal_point: Offset = Offset(d.get("fp_x"), d.get("fp_y")) - self.local_focal_point: Offset = Offset(d.get("lfp_x"), d.get("lfp_y")) - self.scale: float = d.get("s") - self.horizontal_scale: float = d.get("hs") - self.vertical_scale: float = d.get("vs") - self.rotation: float = d.get("rot") - - -class InteractiveViewerInteractionEndEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.pointer_count: int = d.get("pc") - self.scale_velocity: float = d.get("sv") - - -class InteractiveViewer(ConstrainedControl, AdaptiveControl): - """ - InteractiveViewer allows users to pan, zoom, and rotate content. - - ----- - - Online docs: https://flet.dev/docs/controls/interactiveviewer - """ - - def __init__( - self, - content: Control, - pan_enabled: Optional[bool] = None, - scale_enabled: Optional[bool] = None, - trackpad_scroll_causes_scale: Optional[bool] = None, - constrained: Optional[bool] = None, - max_scale: OptionalNumber = None, - min_scale: OptionalNumber = None, - interaction_end_friction_coefficient: OptionalNumber = None, - scale_factor: OptionalNumber = None, - clip_behavior: Optional[ClipBehavior] = None, - alignment: Optional[Alignment] = None, - boundary_margin: Optional[MarginValue] = None, - interaction_update_interval: Optional[int] = None, - on_interaction_start: Optional[ - Callable[[InteractiveViewerInteractionStartEvent], None] - ] = None, - on_interaction_update: Optional[ - Callable[[InteractiveViewerInteractionUpdateEvent], None] - ] = None, - on_interaction_end: Optional[ - Callable[[InteractiveViewerInteractionEndEvent], None] - ] = None, - # - # ConstrainedControl and AdaptiveControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.__on_interaction_start = EventHandler( - lambda e: InteractiveViewerInteractionStartEvent(e) - ) - self._add_event_handler( - "interaction_start", self.__on_interaction_start.get_handler() - ) - self.__on_interaction_update = EventHandler( - lambda e: InteractiveViewerInteractionUpdateEvent(e) - ) - self._add_event_handler( - "interaction_update", self.__on_interaction_update.get_handler() - ) - self.__on_interaction_end = EventHandler( - lambda e: InteractiveViewerInteractionEndEvent(e) - ) - self._add_event_handler( - "interaction_end", self.__on_interaction_end.get_handler() - ) - - self.content = content - self.pan_enabled = pan_enabled - self.scale_enabled = scale_enabled - self.trackpad_scroll_causes_scale = trackpad_scroll_causes_scale - self.constrained = constrained - self.max_scale = max_scale - self.min_scale = min_scale - self.interaction_end_friction_coefficient = interaction_end_friction_coefficient - self.scale_factor = scale_factor - self.clip_behavior = clip_behavior - self.alignment = alignment - self.boundary_margin = boundary_margin - self.on_interaction_start = on_interaction_start - self.on_interaction_end = on_interaction_end - self.on_interaction_update = on_interaction_update - self.interaction_update_interval = interaction_update_interval - - def _get_control_name(self): - return "interactiveviewer" - - def before_update(self): - super().before_update() - assert self.__content.visible, "content must be visible" - assert ( - self.max_scale >= self.min_scale - ), "max_scale must be greather than or equal to min_scale" - self._set_attr_json("alignment", self.__alignment) - self._set_attr_json("boundaryMargin", self.__boundary_margin) - - def _get_children(self): - return [self.__content] - - def reset(self, animation_duration: Optional[DurationValue] = None): - self.invoke_method( - "reset", arguments={"duration": self._convert_attr_json(animation_duration)} - ) - - def save_state(self): - self.invoke_method("save_state") - - def restore_state(self): - self.invoke_method("restore_state") - - def zoom(self, factor: Number): - self.invoke_method("zoom", arguments={"factor": str(factor)}) - - def pan(self, dx: Number, dy: Number): - self.invoke_method("pan", arguments={"dx": str(dx), "dy": str(dy)}) - - # min_scale - @property - def min_scale(self) -> float: - return self._get_attr("minScale", data_type="float", def_value=0.8) - - @min_scale.setter - def min_scale(self, value: OptionalNumber): - assert value is None or value > 0, "min_scale must be greater than 0" - self._set_attr("minScale", value) - - # interaction_update_interval - @property - def interaction_update_interval(self) -> int: - return self._get_attr( - "interactionUpdateInterval", data_type="int", def_value=200 - ) - - @interaction_update_interval.setter - def interaction_update_interval(self, value: Optional[int]): - self._set_attr("interactionUpdateInterval", value) - - # max_scale - @property - def max_scale(self) -> float: - return self._get_attr("maxScale", data_type="float", def_value=2.5) - - @max_scale.setter - def max_scale(self, value: OptionalNumber): - assert value is None or value > 0, "max_scale must be greater than 0" - self._set_attr("maxScale", value) - - # interaction_end_friction_coefficient - @property - def interaction_end_friction_coefficient(self) -> float: - return self._get_attr( - "interactionEndFrictionCoefficient", data_type="float", def_value=0.0000135 - ) - - @interaction_end_friction_coefficient.setter - def interaction_end_friction_coefficient(self, value: OptionalNumber): - assert ( - value is None or value > 0 - ), "interaction_end_friction_coefficient must be greater than 0" - self._set_attr("interactionEndFrictionCoefficient", value) - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # pan_enabled - @property - def pan_enabled(self) -> bool: - return self._get_attr("panEnabled", data_type="bool", def_value=True) - - @pan_enabled.setter - def pan_enabled(self, value: Optional[bool]): - self._set_attr("panEnabled", value) - - # scale_enabled - @property - def scale_enabled(self) -> bool: - return self._get_attr("scaleEnabled", data_type="bool", def_value=True) - - @scale_enabled.setter - def scale_enabled(self, value: Optional[bool]): - self._set_attr("scaleEnabled", value) - - # trackpad_scroll_causes_scale - @property - def trackpad_scroll_causes_scale(self) -> bool: - return self._get_attr( - "trackpadScrollCausesScale", data_type="bool", def_value=False - ) - - @trackpad_scroll_causes_scale.setter - def trackpad_scroll_causes_scale(self, value: Optional[bool]): - self._set_attr("trackpadScrollCausesScale", value) - - # constrained - @property - def constrained(self) -> bool: - return self._get_attr("constrained", data_type="bool", def_value=True) - - @constrained.setter - def constrained(self, value: Optional[bool]): - self._set_attr("constrained", value) - - # scale_factor - @property - def scale_factor(self) -> float: - return self._get_attr("scaleFactor", data_type="float", def_value=200) - - @scale_factor.setter - def scale_factor(self, value: OptionalNumber): - self._set_attr("scaleFactor", value) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value - - # boundary_margin - @property - def boundary_margin(self) -> Optional[MarginValue]: - return self.__boundary_margin - - @boundary_margin.setter - def boundary_margin(self, value: Optional[MarginValue]): - self.__boundary_margin = value - - # on_interaction_start - @property - def on_interaction_start( - self, - ) -> OptionalEventCallable[InteractiveViewerInteractionStartEvent]: - return self.__on_interaction_start.handler - - @on_interaction_start.setter - def on_interaction_start( - self, - handler: OptionalEventCallable[InteractiveViewerInteractionStartEvent], - ): - self.__on_interaction_start.handler = handler - - # on_interaction_update - @property - def on_interaction_update( - self, - ) -> OptionalEventCallable[InteractiveViewerInteractionUpdateEvent]: - return self.__on_interaction_update.handler - - @on_interaction_update.setter - def on_interaction_update( - self, - handler: OptionalEventCallable[InteractiveViewerInteractionUpdateEvent], - ): - self.__on_interaction_update.handler = handler - - # on_interaction_end - @property - def on_interaction_end( - self, - ) -> OptionalEventCallable[InteractiveViewerInteractionEndEvent]: - return self.__on_interaction_end.handler - - @on_interaction_end.setter - def on_interaction_end( - self, handler: OptionalEventCallable[InteractiveViewerInteractionEndEvent] - ): - self.__on_interaction_end.handler = handler diff --git a/sdk/python/packages/flet/src/flet/core/list_tile.py b/sdk/python/packages/flet/src/flet/core/list_tile.py deleted file mode 100644 index 3e2b33a81..000000000 --- a/sdk/python/packages/flet/src/flet/core/list_tile.py +++ /dev/null @@ -1,588 +0,0 @@ -from enum import Enum -from typing import Any, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.buttons import OutlinedBorder -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - MouseCursor, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, - UrlTarget, - VisualDensity, -) - - -class ListTileTitleAlignment(Enum): - TOP = "top" - CENTER = "center" - BOTTOM = "bottom" - THREE_LINE = "threeLine" - TITLE_HEIGHT = "titleHeight" - - -class ListTileStyle(Enum): - LIST = "list" - DRAWER = "drawer" - - -class ListTile(ConstrainedControl, AdaptiveControl): - """ - A single fixed-height row that typically contains some text as well as a leading or trailing icon. - - Example: - - ``` - import flet as ft - - def main(page): - page.title = "ListTile Example" - page.add( - ft.Card( - content=ft.Container( - width=500, - content=ft.Column( - [ - ft.ListTile( - title=ft.Text("One-line list tile"), - ), - ft.ListTile( - leading=ft.Icon(ft.icons.SETTINGS), - title=ft.Text("One-line selected list tile"), - selected=True, - ), - ], - spacing=0, - ), - padding=ft.padding.symmetric(vertical=10), - ) - ) - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/listtile - """ - - def __init__( - self, - title: Optional[Control] = None, - subtitle: Optional[Control] = None, - is_three_line: Optional[bool] = None, - leading: Optional[Control] = None, - trailing: Optional[Control] = None, - content_padding: PaddingValue = None, - bgcolor: Optional[ColorValue] = None, - bgcolor_activated: Optional[str] = None, - hover_color: Optional[ColorValue] = None, - selected: Optional[bool] = None, - dense: Optional[bool] = None, - autofocus: Optional[bool] = None, - toggle_inputs: Optional[bool] = None, - selected_color: Optional[ColorValue] = None, - selected_tile_color: Optional[ColorValue] = None, - style: Optional[ListTileStyle] = None, - enable_feedback: Optional[bool] = None, - horizontal_spacing: OptionalNumber = None, - min_leading_width: OptionalNumber = None, - min_vertical_padding: OptionalNumber = None, - url: Optional[str] = None, - url_target: Optional[UrlTarget] = None, - title_alignment: Optional[ListTileTitleAlignment] = None, - icon_color: Optional[ColorValue] = None, - text_color: Optional[ColorValue] = None, - shape: Optional[OutlinedBorder] = None, - visual_density: Optional[VisualDensity] = None, - mouse_cursor: Optional[MouseCursor] = None, - title_text_style: Optional[TextStyle] = None, - subtitle_text_style: Optional[TextStyle] = None, - leading_and_trailing_text_style: Optional[TextStyle] = None, - min_height: OptionalNumber = None, - on_click: OptionalControlEventCallable = None, - on_long_press: OptionalControlEventCallable = None, - # - # ConstrainedControl and AdaptiveControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.content_padding = content_padding - self.leading = leading - self.title = title - self.subtitle = subtitle - self.trailing = trailing - self.is_three_line = is_three_line - self.selected = selected - self.dense = dense - self.autofocus = autofocus - self.toggle_inputs = toggle_inputs - self.url = url - self.url_target = url_target - self.bgcolor = bgcolor - self.bgcolor_activated = bgcolor_activated - self.hover_color = hover_color - self.on_click = on_click - self.on_long_press = on_long_press - self.style = style - self.selected_color = selected_color - self.selected_tile_color = selected_tile_color - self.enable_feedback = enable_feedback - self.horizontal_spacing = horizontal_spacing - self.min_leading_width = min_leading_width - self.min_vertical_padding = min_vertical_padding - self.title_alignment = title_alignment - self.icon_color = icon_color - self.text_color = text_color - self.shape = shape - self.visual_density = visual_density - self.mouse_cursor = mouse_cursor - self.title_text_style = title_text_style - self.subtitle_text_style = subtitle_text_style - self.leading_and_trailing_text_style = leading_and_trailing_text_style - self.min_height = min_height - - def _get_control_name(self): - return "listtile" - - def before_update(self): - super().before_update() - self._set_attr_json("contentPadding", self.__content_padding) - if isinstance(self.__shape, OutlinedBorder): - self._set_attr_json("shape", self.__shape) - if isinstance(self.__title_text_style, TextStyle): - self._set_attr_json("titleTextStyle", self.__title_text_style) - if isinstance(self.__subtitle_text_style, TextStyle): - self._set_attr_json("subtitleTextStyle", self.__subtitle_text_style) - if isinstance(self.__leading_and_trailing_text_style, TextStyle): - self._set_attr_json( - "leadingAndTrailingTextStyle", self.__leading_and_trailing_text_style - ) - - def _get_children(self): - children = [] - if self.__leading: - self.__leading._set_attr_internal("n", "leading") - children.append(self.__leading) - if self.__title: - self.__title._set_attr_internal("n", "title") - children.append(self.__title) - if self.__subtitle: - self.__subtitle._set_attr_internal("n", "subtitle") - children.append(self.__subtitle) - if self.__trailing: - self.__trailing._set_attr_internal("n", "trailing") - children.append(self.__trailing) - return children - - # content_padding - @property - def content_padding(self) -> Optional[PaddingValue]: - return self.__content_padding - - @content_padding.setter - def content_padding(self, value: Optional[PaddingValue]): - self.__content_padding = value - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # selected_color - @property - def selected_color(self) -> Optional[ColorValue]: - return self.__selected_color - - @selected_color.setter - def selected_color(self, value: Optional[ColorValue]): - self.__selected_color = value - self._set_enum_attr("selectedColor", value, ColorEnums) - - # selected_tile_color - @property - def selected_tile_color(self) -> Optional[ColorValue]: - return self.__selected_tile_color - - @selected_tile_color.setter - def selected_tile_color(self, value: Optional[ColorValue]): - self.__selected_tile_color = value - self._set_enum_attr("selectedTileColor", value, ColorEnums) - - # bgcolor_activated - @property - def bgcolor_activated(self) -> Optional[str]: - return self._get_attr("bgcolorActivated") - - @bgcolor_activated.setter - def bgcolor_activated(self, value: Optional[str]): - self._set_attr("bgcolorActivated", value) - - # min_leading_width - @property - def min_leading_width(self) -> float: - return self._get_attr("minLeadingWidth", data_type="float", def_value=40.0) - - @min_leading_width.setter - def min_leading_width(self, value: OptionalNumber): - self._set_attr("minLeadingWidth", value) - - # horizontal_spacing - @property - def horizontal_spacing(self) -> float: - return self._get_attr("horizontalSpacing", data_type="float", def_value=16.0) - - @horizontal_spacing.setter - def horizontal_spacing(self, value: OptionalNumber): - self._set_attr("horizontalSpacing", value) - - # min_height - @property - def min_height(self) -> OptionalNumber: - return self._get_attr("minHeight", data_type="float") - - @min_height.setter - def min_height(self, value: OptionalNumber): - self._set_attr("minHeight", value) - - # hover_color - @property - def hover_color(self) -> Optional[ColorValue]: - return self.__hover_color - - @hover_color.setter - def hover_color(self, value: Optional[ColorValue]): - self.__hover_color = value - self._set_enum_attr("hoverColor", value, ColorEnums) - - # leading - @property - def leading(self) -> Optional[Control]: - return self.__leading - - @leading.setter - def leading(self, value: Optional[Control]): - self.__leading = value - - # title - @property - def title(self) -> Optional[Control]: - return self.__title - - @title.setter - def title(self, value: Optional[Control]): - self.__title = value - - # min_vertical_padding - @property - def min_vertical_padding(self) -> float: - return self._get_attr("minVerticalPadding", data_type="float", def_value=4.0) - - @min_vertical_padding.setter - def min_vertical_padding(self, value: OptionalNumber): - self._set_attr("minVerticalPadding", value) - - # subtitle - @property - def subtitle(self) -> Optional[Control]: - return self.__subtitle - - @subtitle.setter - def subtitle(self, value: Optional[Control]): - self.__subtitle = value - - # trailing - @property - def trailing(self) -> Optional[Control]: - return self.__trailing - - @trailing.setter - def trailing(self, value: Optional[Control]): - self.__trailing = value - - # is_three_line - @property - def is_three_line(self) -> bool: - return self._get_attr("isThreeLine", data_type="bool", def_value=False) - - @is_three_line.setter - def is_three_line(self, value: Optional[bool]): - self._set_attr("isThreeLine", value) - - # enable_feedback - @property - def enable_feedback(self) -> bool: - return self._get_attr("enableFeedback", data_type="bool", def_value=True) - - @enable_feedback.setter - def enable_feedback(self, value: Optional[bool]): - self._set_attr("enableFeedback", value) - - # style - @property - def style(self) -> Optional[ListTileStyle]: - return self.__style - - @style.setter - def style(self, value: Optional[ListTileStyle]): - self.__style = value - self._set_enum_attr("style", value, ListTileStyle) - - # title_alignment - @property - def title_alignment(self) -> Optional[ListTileTitleAlignment]: - return self.__title_alignment - - @title_alignment.setter - def title_alignment(self, value: Optional[ListTileTitleAlignment]): - self.__title_alignment = value - self._set_enum_attr("titleAlignment", value, ListTileTitleAlignment) - - # selected - @property - def selected(self) -> bool: - return self._get_attr("selected", data_type="bool", def_value=False) - - @selected.setter - def selected(self, value: Optional[bool]): - self._set_attr("selected", value) - - # dense - @property - def dense(self) -> bool: - return self._get_attr("dense", data_type="bool", def_value=False) - - @dense.setter - def dense(self, value: Optional[bool]): - self._set_attr("dense", value) - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # toggle_inputs - @property - def toggle_inputs(self) -> bool: - return self._get_attr("toggleInputs", data_type="bool", def_value=False) - - @toggle_inputs.setter - def toggle_inputs(self, value: Optional[bool]): - self._set_attr("toggleInputs", value) - - # url - @property - def url(self) -> Optional[str]: - return self._get_attr("url") - - @url.setter - def url(self, value: Optional[str]): - self._set_attr("url", value) - - # icon_color - @property - def icon_color(self) -> Optional[ColorValue]: - return self.__icon_color - - @icon_color.setter - def icon_color(self, value: Optional[ColorValue]): - self.__icon_color = value - self._set_enum_attr("iconColor", value, ColorEnums) - - # text_color - @property - def text_color(self) -> Optional[ColorValue]: - return self.__text_color - - @text_color.setter - def text_color(self, value: Optional[ColorValue]): - self.__text_color = value - self._set_enum_attr("textColor", value, ColorEnums) - - # url_target - @property - def url_target(self) -> Optional[UrlTarget]: - return self.__url_target - - @url_target.setter - def url_target(self, value: Optional[UrlTarget]): - self.__url_target = value - self._set_enum_attr("urlTarget", value, UrlTarget) - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self.__mouse_cursor - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self.__mouse_cursor = value - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # visual_density - @property - def visual_density(self) -> Optional[VisualDensity]: - return self.__visual_density - - @visual_density.setter - def visual_density(self, value: Optional[VisualDensity]): - self.__visual_density = value - self._set_enum_attr("visualDensity", value, VisualDensity) - - # shape - @property - def shape(self) -> Optional[OutlinedBorder]: - return self.__shape - - @shape.setter - def shape(self, value: Optional[OutlinedBorder]): - self.__shape = value - - # title_text_style - @property - def title_text_style(self) -> Optional[TextStyle]: - return self.__title_text_style - - @title_text_style.setter - def title_text_style(self, value: Optional[TextStyle]): - self.__title_text_style = value - - # subtitle_text_style - @property - def subtitle_text_style(self) -> Optional[TextStyle]: - return self.__subtitle_text_style - - @subtitle_text_style.setter - def subtitle_text_style(self, value: Optional[TextStyle]): - self.__subtitle_text_style = value - - # leading_and_trailing_text_style - @property - def leading_and_trailing_text_style(self) -> Optional[TextStyle]: - return self.__leading_and_trailing_text_style - - @leading_and_trailing_text_style.setter - def leading_and_trailing_text_style(self, value: Optional[TextStyle]): - self.__leading_and_trailing_text_style = value - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) - self._set_attr("onclick", True if handler is not None else None) - - # on_long_press - @property - def on_long_press(self) -> OptionalControlEventCallable: - return self._get_event_handler("long_press") - - @on_long_press.setter - def on_long_press(self, handler: OptionalControlEventCallable): - self._add_event_handler("long_press", handler) - self._set_attr("onLongPress", True if handler is not None else None) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) diff --git a/sdk/python/packages/flet/src/flet/core/list_view.py b/sdk/python/packages/flet/src/flet/core/list_view.py deleted file mode 100644 index f74389d26..000000000 --- a/sdk/python/packages/flet/src/flet/core/list_view.py +++ /dev/null @@ -1,279 +0,0 @@ -from typing import Any, List, Optional, Sequence, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.scrollable_control import OnScrollEvent, ScrollableControl -from flet.core.types import ( - ClipBehavior, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class ListView(ConstrainedControl, ScrollableControl, AdaptiveControl): - """ - A scrollable list of controls arranged linearly. - - ListView is the most commonly used scrolling control. It displays its children one after another in the scroll direction. In the cross axis, the children are required to fill the ListView. - - Example: - - ``` - from time import sleep - import flet as ft - - def main(page: ft.Page): - page.title = "Auto-scrolling ListView" - - lv = ft.ListView(expand=1, spacing=10, padding=20, auto_scroll=True) - - count = 1 - - for i in range(0, 60): - lv.controls.append(ft.Text(f"Line {count}")) - count += 1 - - page.add(lv) - - for i in range(0, 60): - sleep(1) - lv.controls.append(ft.Text(f"Line {count}")) - count += 1 - page.update() - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/listview - """ - - def __init__( - self, - controls: Optional[Sequence[Control]] = None, - horizontal: Optional[bool] = None, - spacing: OptionalNumber = None, - item_extent: OptionalNumber = None, - first_item_prototype: Optional[bool] = None, - divider_thickness: OptionalNumber = None, - padding: Optional[PaddingValue] = None, - clip_behavior: Optional[ClipBehavior] = None, - semantic_child_count: Optional[int] = None, - cache_extent: OptionalNumber = None, - build_controls_on_demand: Optional[bool] = None, - # - # ScrollableControl specific - # - auto_scroll: Optional[bool] = None, - reverse: Optional[bool] = None, - on_scroll_interval: OptionalNumber = None, - on_scroll: OptionalEventCallable[OnScrollEvent] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - # - # AdaptiveControl - # - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - ) - - ScrollableControl.__init__( - self, - auto_scroll=auto_scroll, - reverse=reverse, - on_scroll_interval=on_scroll_interval, - on_scroll=on_scroll, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.__controls: List[Control] = [] - self.controls = controls - self.horizontal = horizontal - self.spacing = spacing - self.divider_thickness = divider_thickness - self.item_extent = item_extent - self.first_item_prototype = first_item_prototype - self.padding = padding - self.clip_behavior = clip_behavior - self.semantic_child_count = semantic_child_count - self.cache_extent = cache_extent - self.build_controls_on_demand = build_controls_on_demand - - def _get_control_name(self): - return "listview" - - def before_update(self): - super().before_update() - self._set_attr_json("padding", self.__padding) - - def _get_children(self): - return self.__controls - - def clean(self): - super().clean() - self.__controls.clear() - - # horizontal - @property - def horizontal(self) -> bool: - return self._get_attr("horizontal", data_type="bool", def_value=False) - - @horizontal.setter - def horizontal(self, value: Optional[bool]): - self._set_attr("horizontal", value) - - # spacing - @property - def spacing(self) -> OptionalNumber: - return self._get_attr("spacing", data_type="float") - - @spacing.setter - def spacing(self, value: OptionalNumber): - self._set_attr("spacing", value) - - # divider_thickness - @property - def divider_thickness(self) -> OptionalNumber: - return self._get_attr("dividerThickness") - - @divider_thickness.setter - def divider_thickness(self, value: OptionalNumber): - self._set_attr("dividerThickness", value) - - # item_extent - @property - def item_extent(self) -> OptionalNumber: - return self._get_attr("itemExtent") - - @item_extent.setter - def item_extent(self, value: OptionalNumber): - self._set_attr("itemExtent", value) - - # cache_extent - @property - def cache_extent(self) -> OptionalNumber: - return self._get_attr("cacheExtent", data_type="float") - - @cache_extent.setter - def cache_extent(self, value: OptionalNumber): - self._set_attr("cacheExtent", value) - - # first_item_prototype - @property - def first_item_prototype(self) -> bool: - return self._get_attr("firstItemPrototype", data_type="bool", def_value=False) - - @first_item_prototype.setter - def first_item_prototype(self, value: Optional[bool]): - self._set_attr("firstItemPrototype", value) - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # controls - @property - def controls(self) -> List[Control]: - return self.__controls - - @controls.setter - def controls(self, value: Optional[Sequence[Control]]): - self.__controls = list(value) if value is not None else [] - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # semantic_child_count - @property - def semantic_child_count(self) -> Optional[int]: - return self._get_attr("semanticChildCount", data_type="int") - - @semantic_child_count.setter - def semantic_child_count(self, value: Optional[int]): - self._set_attr("semanticChildCount", value) - - # build_controls_on_demand - @property - def build_controls_on_demand(self) -> Optional[bool]: - return self._get_attr("buildControlsOnDemand", data_type="bool", def_value=True) - - @build_controls_on_demand.setter - def build_controls_on_demand(self, value: Optional[bool]): - self._set_attr("buildControlsOnDemand", value) diff --git a/sdk/python/packages/flet/src/flet/core/local_connection.py b/sdk/python/packages/flet/src/flet/core/local_connection.py deleted file mode 100644 index 19f63c17c..000000000 --- a/sdk/python/packages/flet/src/flet/core/local_connection.py +++ /dev/null @@ -1,261 +0,0 @@ -import logging - -import flet.core -from flet.core.connection import Connection -from flet.core.protocol import * - -logger = logging.getLogger(flet.__name__) - - -class LocalConnection(Connection): - def __init__(self): - super().__init__() - self.__control_id = 1 - self._client_details = None - - def _create_register_web_client_response( - self, controls: Optional[Dict[str, Dict[str, Any]]] = None - ): - assert self._client_details - return ClientMessage( - ClientActions.REGISTER_WEB_CLIENT, - RegisterWebClientResponsePayload( - session=SessionPayload( - id=self._client_details.sessionId, - controls=( - controls - if controls is not None - else { - "page": { - "i": "page", - "t": "page", - "p": "", - "c": [], - "route": self._client_details.pageRoute, - "width": self._client_details.pageWidth, - "height": self._client_details.pageHeight, - "windowwidth": self._client_details.windowWidth, - "windowheight": self._client_details.windowHeight, - "windowtop": self._client_details.windowTop, - "windowleft": self._client_details.windowLeft, - "pwa": self._client_details.isPWA, - "web": self._client_details.isWeb, - "debug": self._client_details.isDebug, - "platform": self._client_details.platform, - "platformBrightness": self._client_details.platformBrightness, - "media": self._client_details.media, - } - } - ), - ), - appInactive=False, - error="", - ), - ) - - def _create_session_handler_arg(self): - assert self._client_details - return PageSessionCreatedPayload( - pageName=self._client_details.pageName, - sessionID=self._client_details.sessionId, - ) - - def _create_page_event_handler_arg(self, msg: ClientMessage): - assert self._client_details - web_event = PageEventFromWebPayload(**msg.payload) - return PageEventPayload( - pageName=self._client_details.pageName, - sessionID=self._client_details.sessionId, - eventTarget=web_event.eventTarget, - eventName=web_event.eventName, - eventData=web_event.eventData, - ) - - def _create_update_control_props_handler_arg(self, msg: ClientMessage): - assert self._client_details - return PageEventPayload( - pageName=self._client_details.pageName, - sessionID=self._client_details.sessionId, - eventTarget="page", - eventName="change", - eventData=json.dumps(msg.payload["props"], separators=(",", ":")), - ) - - def _process_command(self, command: Command): - logger.debug(f"_process_command: {command}") - if command.name == "get": - return self._process_get_command(command.values) - elif command.name == "add": - return self._process_add_command(command) - elif command.name == "set": - return self._process_set_command(command.values, command.attrs) - elif command.name == "remove": - return self._process_remove_command(command.values) - elif command.name == "clean": - return self._process_clean_command(command.values) - elif command.name == "invokeMethod": - return self._process_invoke_method_command(command.values, command.attrs) - elif command.name == "error": - return self._process_error_command(command.values) - elif command.name == "getUploadUrl": - return self._process_get_upload_url_command(command.attrs) - elif command.name == "oauthAuthorize": - return self._process_oauth_authorize_command(command.attrs) - raise Exception(f"Unsupported command: {command.name}") - - def _process_add_command(self, command: Command): - top_parent_id = command.attrs.get("to", "page") - top_parent_at = int(command.attrs.get("at", "-1")) - - batch: List[Command] = [] - if len(command.values) > 0: - batch.append(command) - - for sub_cmd in command.commands: - sub_cmd.name = "add" - batch.append(sub_cmd) - - ids = [] - controls = [] - controls_idx = {} - - i = 0 - for cmd in batch: - assert len(cmd.values) > 0, "control type is not specified" - control_type = cmd.values[0].lower() - - parent_id = "" - parent_at = -1 - - # find nearest parentID - pi = i - 1 - while pi >= 0: - if batch[pi].indent < cmd.indent: - parent_id = batch[pi].attrs.get("id", "") - break - pi -= 1 - - # parent wasn't found - use the topmost one - if parent_id == "": - parent_id = top_parent_id - parent_at = top_parent_at - - id = cmd.attrs.get("id", "") - if not id: - id = f"_{self._get_next_control_id()}" - cmd.attrs["id"] = id - - ids.append(id) - - control = {"t": control_type, "i": id, "p": parent_id, "c": []} - controls.append(control) - controls_idx[id] = control - - if parent_at != -1: - control["at"] = str(parent_at) - top_parent_at += 1 - - parent_control = controls_idx.get(parent_id) - if parent_control: - if parent_at != -1: - parent_control["c"].insert(parent_at, id) - else: - parent_control["c"].append(id) - - system_attrs = ["id", "to", "from", "at", "t", "p", "i", "c"] - for k, v in cmd.attrs.items(): - if k not in system_attrs and v: - control[k] = v - - i += 1 - - return " ".join(ids), ClientMessage( - ClientActions.ADD_PAGE_CONTROLS, AddPageControlsPayload(controls=controls) - ) - - def _process_set_command(self, values, attrs): - assert len(values) == 1, '"set" command has wrong number of values' - props = {"i": values[0]} - for k, v in attrs.items(): - props[k] = v - - return "", ClientMessage( - ClientActions.UPDATE_CONTROL_PROPS, UpdateControlPropsPayload(props=[props]) - ) - - def _process_remove_command(self, values): - assert len(values) > 0, '"remove" command has wrong number of values' - return "", ClientMessage( - ClientActions.REMOVE_CONTROL, RemoveControlPayload(ids=values) - ) - - def _process_clean_command(self, values): - assert len(values) > 0, '"clean" command has wrong number of values' - return "", ClientMessage( - ClientActions.CLEAN_CONTROL, CleanControlPayload(ids=values) - ) - - def _process_error_command(self, values): - assert len(values) == 1, '"error" command has wrong number of values' - return "", ClientMessage( - ClientActions.SESSION_CRASHED, SessionCrashedPayload(message=values[0]) - ) - - def _process_invoke_method_command(self, values, attrs): - # "invokeMethod", values=[method_id, method_name], attrs=arguments - assert len(values) == 3, '"invokeMethod" command has wrong number of values' - return "", ClientMessage( - ClientActions.INVOKE_METHOD, - InvokeMethodPayload( - methodId=values[0], - methodName=values[1], - controlId=values[2], - arguments=attrs, - ), - ) - - def _process_get_upload_url_command(self, attrs): - raise Exception("getUploadUrl command is not supported.") - - def _process_oauth_authorize_command(self, attrs): - raise Exception("oauthAuthorize command is not supported.") - - def _process_get_command(self, values: List[str]): - assert len(values) == 2, '"get" command has wrong number of values' - assert self._client_details - ctrl_id = values[0] - prop_name = values[1] - r = "" - if ctrl_id == "page": - if prop_name == "route": - r = self._client_details.pageRoute - elif prop_name == "pwa": - r = self._client_details.isPWA - elif prop_name == "web": - r = self._client_details.isWeb - elif prop_name == "debug": - r = self._client_details.isDebug - elif prop_name == "platform": - r = self._client_details.platform - elif prop_name == "platformBrightness": - r = self._client_details.platformBrightness - elif prop_name == "media": - r = self._client_details.media - elif prop_name == "width": - r = self._client_details.pageWidth - elif prop_name == "height": - r = self._client_details.pageHeight - elif prop_name == "windowWidth": - r = self._client_details.windowWidth - elif prop_name == "windowHeight": - r = self._client_details.windowHeight - elif prop_name == "windowTop": - r = self._client_details.windowTop - elif prop_name == "windowLeft": - r = self._client_details.windowLeft - return r, None - - def _get_next_control_id(self): - r = self.__control_id - self.__control_id += 1 - return r diff --git a/sdk/python/packages/flet/src/flet/core/lottie.py b/sdk/python/packages/flet/src/flet/core/lottie.py deleted file mode 100644 index 9b887fb71..000000000 --- a/sdk/python/packages/flet/src/flet/core/lottie.py +++ /dev/null @@ -1,208 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.box import FilterQuality -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ImageFit, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) -from flet.utils import deprecated - - -@deprecated( - reason="Lottie control has been moved to a separate Python package: https://pypi.org/project/flet-lottie. " - + "Read more about this change in Flet blog: https://flet.dev/blog/flet-v-0-26-release-announcement", - version="0.26.0", - delete_version="0.29.0", -) -class Lottie(ConstrainedControl): - """ - Displays lottie animations. - - ----- - - Online docs: https://flet.dev/docs/controls/lottie - """ - - def __init__( - self, - src: Optional[str] = None, - src_base64: Optional[str] = None, - repeat: Optional[bool] = None, - reverse: Optional[bool] = None, - animate: Optional[bool] = None, - background_loading: Optional[bool] = None, - filter_quality: Optional[FilterQuality] = None, - fit: Optional[ImageFit] = None, - on_error=None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - rtl=rtl, - ) - - self.src = src - self.src_base64 = src_base64 - self.repeat = repeat - self.reverse = reverse - self.animate = animate - self.filter_quality = filter_quality - self.fit = fit - self.background_loading = background_loading - self.on_error = on_error - - def _get_control_name(self): - return "lottie" - - # src - @property - def src(self) -> Optional[str]: - return self._get_attr("src") - - @src.setter - def src(self, value: Optional[str]): - self._set_attr("src", value) - - # src_base64 - @property - def src_base64(self) -> Optional[str]: - return self._get_attr("srcBase64") - - @src_base64.setter - def src_base64(self, value: Optional[str]): - self._set_attr("srcBase64", value) - - # repeat - @property - def repeat(self) -> bool: - return self._get_attr("repeat", def_value=True, data_type="bool") - - @repeat.setter - def repeat(self, value: Optional[bool]): - self._set_attr("repeat", value) - - # animate - @property - def animate(self) -> bool: - return self._get_attr("animate", def_value=True, data_type="bool") - - @animate.setter - def animate(self, value: Optional[bool]): - self._set_attr("animate", value) - - # reverse - @property - def reverse(self) -> bool: - return self._get_attr("reverse", def_value=False, data_type="bool") - - @reverse.setter - def reverse(self, value: Optional[bool]): - self._set_attr("reverse", value) - - # filter_quality - @property - def filter_quality(self) -> Optional[FilterQuality]: - return self.__filter_quality - - @filter_quality.setter - def filter_quality(self, value: Optional[FilterQuality]): - self.__filter_quality = value - self._set_enum_attr("filterQuality", value, FilterQuality) - - # fit - @property - def fit(self) -> Optional[ImageFit]: - return self.__fit - - @fit.setter - def fit(self, value: Optional[ImageFit]): - self.__fit = value - self._set_enum_attr("fit", value, ImageFit) - - # background_loading - @property - def background_loading(self) -> Optional[bool]: - return self._get_attr("backgroundLoading", data_type="bool") - - @background_loading.setter - def background_loading(self, value: Optional[bool]): - self._set_attr("backgroundLoading", value) - - # on_error - @property - def on_error(self) -> OptionalControlEventCallable: - return self._get_event_handler("error") - - @on_error.setter - def on_error(self, handler: OptionalControlEventCallable): - self._add_event_handler("error", handler) - self._set_attr("onError", True if handler is not None else None) diff --git a/sdk/python/packages/flet/src/flet/core/map/circle_layer.py b/sdk/python/packages/flet/src/flet/core/map/circle_layer.py deleted file mode 100644 index 27125fec5..000000000 --- a/sdk/python/packages/flet/src/flet/core/map/circle_layer.py +++ /dev/null @@ -1,156 +0,0 @@ -from typing import Any, List, Optional, Union - -from flet.core.control import Control, OptionalNumber -from flet.core.map.map import MapLatitudeLongitude -from flet.core.map.map_layer import MapLayer -from flet.core.ref import Ref -from flet.core.types import ColorEnums, ColorValue - - -class CircleMarker(Control): - """ - A circular marker displayed on the Map at the specified location through the CircleLayer. - - ----- - - Online docs: https://flet.dev/docs/controls/mapcirclemarker - """ - - def __init__( - self, - radius: Union[int, float], - coordinates: MapLatitudeLongitude, - color: Optional[ColorValue] = None, - border_color: Optional[ColorValue] = None, - border_stroke_width: OptionalNumber = None, - use_radius_in_meter: Optional[bool] = None, - # - # Control - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - visible=visible, - data=data, - ) - - self.coordinates = coordinates - self.color = color - self.border_color = border_color - self.border_stroke_width = border_stroke_width - self.use_radius_in_meter = use_radius_in_meter - self.radius = radius - - def _get_control_name(self): - return "map_circle_marker" - - def before_update(self): - super().before_update() - self._set_attr_json("coordinates", self.__coordinates) - - # use_radius_in_meter - @property - def use_radius_in_meter(self) -> bool: - return self._get_attr("useRadiusInMeter", data_type="bool", def_value=False) - - @use_radius_in_meter.setter - def use_radius_in_meter(self, value: Optional[bool]): - self._set_attr("useRadiusInMeter", value) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # border_color - @property - def border_color(self) -> Optional[ColorValue]: - return self.__border_color - - @border_color.setter - def border_color(self, value: Optional[ColorValue]): - self.__border_color = value - self._set_enum_attr("borderColor", value, ColorEnums) - - # radius - @property - def radius(self) -> Union[int, float]: - return self._get_attr("radius", data_type="float") - - @radius.setter - def radius(self, value: Union[int, float]): - self._set_attr("radius", value) - - # border_stroke_width - @property - def border_stroke_width(self) -> OptionalNumber: - return self._get_attr("borderStrokeWidth", data_type="float") - - @border_stroke_width.setter - def border_stroke_width(self, value: OptionalNumber): - assert value is None or value >= 0, "border_stroke_width cannot be negative" - self._set_attr("borderStrokeWidth", value) - - # coordinates - @property - def coordinates(self) -> MapLatitudeLongitude: - return self.__coordinates - - @coordinates.setter - def coordinates(self, value: MapLatitudeLongitude): - self.__coordinates = value - - -class CircleLayer(MapLayer): - """ - A layer to display CircleMarkers. - - ----- - - Online docs: https://flet.dev/docs/controls/mapcirclelayer - """ - - def __init__( - self, - circles: List[CircleMarker], - # - # MapLayer - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - MapLayer.__init__( - self, - ref=ref, - visible=visible, - data=data, - ) - - self.circles = circles - - def _get_control_name(self): - return "map_circle_layer" - - def _get_children(self): - return self.__circles - - # circles - @property - def circles(self) -> List[CircleMarker]: - return self.__circles - - @circles.setter - def circles(self, value: List[CircleMarker]): - self.__circles = value diff --git a/sdk/python/packages/flet/src/flet/core/map/map.py b/sdk/python/packages/flet/src/flet/core/map/map.py deleted file mode 100644 index 3617f2931..000000000 --- a/sdk/python/packages/flet/src/flet/core/map/map.py +++ /dev/null @@ -1,653 +0,0 @@ -import json -from dataclasses import dataclass -from enum import Enum, IntFlag -from typing import Any, List, Optional, Tuple, Union - -from flet.core.animation import AnimationCurve, AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.event_handler import EventHandler -from flet.core.map.map_layer import MapLayer -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.transform import Offset -from flet.core.types import ( - ColorEnums, - ColorValue, - ControlEvent, - DurationValue, - Number, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - PointerDeviceType, - ResponsiveNumber, - RotateValue, - ScaleValue, -) -from flet.utils import deprecated - - -@dataclass -class MapLatitudeLongitude: - latitude: Union[float, int] - longitude: Union[float, int] - - -@dataclass -class MapLatitudeLongitudeBounds: - corner_1: MapLatitudeLongitude - corner_2: MapLatitudeLongitude - - -class MapInteractiveFlag(IntFlag): - NONE = 0 - DRAG = 1 << 0 - FLING_ANIMATION = 1 << 1 - PINCH_MOVE = 1 << 2 - PINCH_ZOOM = 1 << 3 - DOUBLE_TAP_ZOOM = 1 << 4 - DOUBLE_TAP_DRAG_ZOOM = 1 << 5 - SCROLL_WHEEL_ZOOM = 1 << 6 - ROTATE = 1 << 7 - ALL = ( - (1 << 0) - | (1 << 1) - | (1 << 2) - | (1 << 3) - | (1 << 4) - | (1 << 5) - | (1 << 6) - | (1 << 7) - ) - - -class MapMultiFingerGesture(IntFlag): - NONE = 0 - PINCH_MOVE = 1 << 0 - PINCH_ZOOM = 1 << 1 - ROTATE = 1 << 2 - ALL = (1 << 0) | (1 << 1) | (1 << 2) - - -@dataclass -class MapInteractionConfiguration: - enable_multi_finger_gesture_race: Optional[bool] = None - pinch_move_threshold: OptionalNumber = None - scroll_wheel_velocity: OptionalNumber = None - pinch_zoom_threshold: OptionalNumber = None - rotation_threshold: OptionalNumber = None - flags: Optional[MapInteractiveFlag] = None - rotation_win_gestures: Optional[MapMultiFingerGesture] = None - pinch_move_win_gestures: Optional[MapMultiFingerGesture] = None - pinch_zoom_win_gestures: Optional[MapMultiFingerGesture] = None - - -@deprecated( - reason="Map control has been moved to a separate Python package: https://pypi.org/project/flet-map. " - + "Read more about this change in Flet blog: https://flet.dev/blog/flet-v-0-26-release-announcement", - version="0.26.0", - delete_version="0.29.0", -) -class Map(ConstrainedControl): - """ - Map Control. - - ----- - - Online docs: https://flet.dev/docs/controls/map - """ - - def __init__( - self, - layers: List[MapLayer], - initial_center: Optional[MapLatitudeLongitude] = None, - initial_rotation: OptionalNumber = None, - initial_zoom: OptionalNumber = None, - interaction_configuration: Optional[MapInteractionConfiguration] = None, - bgcolor: Optional[ColorValue] = None, - keep_alive: Optional[bool] = None, - max_zoom: OptionalNumber = None, - min_zoom: OptionalNumber = None, - animation_curve: Optional[AnimationCurve] = None, - animation_duration: Optional[DurationValue] = None, - on_init: OptionalControlEventCallable = None, - on_tap: OptionalEventCallable["MapTapEvent"] = None, - on_hover: OptionalEventCallable["MapHoverEvent"] = None, - on_secondary_tap: OptionalEventCallable["MapTapEvent"] = None, - on_long_press: OptionalEventCallable["MapTapEvent"] = None, - on_event: OptionalEventCallable["MapEvent"] = None, - on_position_change: OptionalEventCallable["MapPositionChangeEvent"] = None, - on_pointer_down: OptionalEventCallable["MapPointerEvent"] = None, - on_pointer_cancel: OptionalEventCallable["MapPointerEvent"] = None, - on_pointer_up: OptionalEventCallable["MapPointerEvent"] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - self.__on_tap = EventHandler(lambda e: MapTapEvent(e)) - self._add_event_handler("tap", self.__on_tap.get_handler()) - - self.__on_hover = EventHandler(lambda e: MapHoverEvent(e)) - self._add_event_handler("hover", self.__on_hover.get_handler()) - - self.__on_secondary_tap = EventHandler(lambda e: MapTapEvent(e)) - self._add_event_handler("secondary_tap", self.__on_secondary_tap.get_handler()) - - self.__on_long_press = EventHandler(lambda e: MapTapEvent(e)) - self._add_event_handler("long_press", self.__on_long_press.get_handler()) - - self.__on_event = EventHandler(lambda e: MapEvent(e)) - self._add_event_handler("event", self.__on_event.get_handler()) - - self.__on_position_change = EventHandler(lambda e: MapPositionChangeEvent(e)) - self._add_event_handler( - "position_change", self.__on_position_change.get_handler() - ) - - self.__on_pointer_down = EventHandler(lambda e: MapPointerEvent(e)) - self._add_event_handler("pointer_down", self.__on_pointer_down.get_handler()) - - self.__on_pointer_cancel = EventHandler(lambda e: MapPointerEvent(e)) - self._add_event_handler( - "pointer_cancel", self.__on_pointer_cancel.get_handler() - ) - - self.__on_pointer_up = EventHandler(lambda e: MapPointerEvent(e)) - self._add_event_handler("pointer_up", self.__on_pointer_up.get_handler()) - - self.layers = layers - self.initial_center = initial_center - self.initial_rotation = initial_rotation - self.initial_zoom = initial_zoom - self.interaction_configuration = interaction_configuration - self.bgcolor = bgcolor - self.keep_alive = keep_alive - self.max_zoom = max_zoom - self.min_zoom = min_zoom - self.animation_curve = animation_curve - self.animation_duration = animation_duration - self.on_tap = on_tap - self.on_hover = on_hover - self.on_secondary_tap = on_secondary_tap - self.on_init = on_init - self.on_long_press = on_long_press - self.on_event = on_event - self.on_position_change = on_position_change - self.on_pointer_down = on_pointer_down - self.on_pointer_cancel = on_pointer_cancel - self.on_pointer_up = on_pointer_up - - def before_update(self): - self._set_attr_json("initialCenter", self.__initial_center) - self._set_attr_json("animationDuration", self.__animation_duration) - self._set_attr_json( - "interactionConfiguration", self.__interaction_configuration - ) - - def rotate_from( - self, - degree: Number, - animation_curve: Optional[AnimationCurve] = None, - ): - self.invoke_method( - "rotate_from", - arguments={ - "degree": degree, - "curve": animation_curve.value if animation_curve else None, - }, - ) - - def reset_rotation( - self, - animation_curve: Optional[AnimationCurve] = None, - animation_duration: Optional[DurationValue] = None, - ): - self.invoke_method( - "reset_rotation", - arguments={ - "curve": animation_curve.value if animation_curve else None, - "duration": self._convert_attr_json(animation_duration), - }, - ) - - def zoom_in( - self, - animation_curve: Optional[AnimationCurve] = None, - animation_duration: Optional[DurationValue] = None, - ): - self.invoke_method( - "zoom_in", - arguments={ - "curve": animation_curve.value if animation_curve else None, - "duration": self._convert_attr_json(animation_duration), - }, - ) - - def zoom_out( - self, - animation_curve: Optional[AnimationCurve] = None, - animation_duration: Optional[DurationValue] = None, - ): - self.invoke_method( - "zoom_out", - arguments={ - "curve": animation_curve.value if animation_curve else None, - "duration": self._convert_attr_json(animation_duration), - }, - ) - - def zoom_to( - self, - zoom: Number, - animation_curve: Optional[AnimationCurve] = None, - animation_duration: Optional[DurationValue] = None, - ): - self.invoke_method( - "zoom_to", - arguments={ - "zoom": zoom, - "curve": animation_curve.value if animation_curve else None, - "duration": self._convert_attr_json(animation_duration), - }, - ) - - def move_to( - self, - destination: Optional[MapLatitudeLongitude] = None, - zoom: OptionalNumber = None, - rotation: OptionalNumber = None, - animation_curve: Optional[AnimationCurve] = None, - animation_duration: Optional[DurationValue] = None, - offset: Optional[Union[Offset, Tuple[Union[Number], Union[Number]]]] = None, - ): - if isinstance(offset, tuple): - offset = Offset(offset[0], offset[1]) - self.invoke_method( - "move_to", - arguments={ - "lat": str(destination.latitude) if destination else None, - "long": str(destination.longitude) if destination else None, - "zoom": zoom, - "ox": str(offset.x) if offset else None, - "oy": str(offset.y) if offset else None, - "rot": rotation, - "curve": animation_curve.value if animation_curve else None, - "duration": self._convert_attr_json(animation_duration), - }, - ) - - def center_on( - self, - point: Optional[MapLatitudeLongitude], - zoom: OptionalNumber, - animation_curve: Optional[AnimationCurve] = None, - animation_duration: Optional[DurationValue] = None, - ): - self.invoke_method( - "center_on", - arguments={ - "lat": str(point.latitude) if point else None, - "long": str(point.longitude) if point else None, - "zoom": zoom, - "curve": animation_curve.value if animation_curve else None, - "duration": self._convert_attr_json(animation_duration), - }, - ) - - def _get_control_name(self): - return "map" - - def _get_children(self): - return self.__layers - - # layers - @property - def layers(self) -> List[MapLayer]: - return self.__layers - - @layers.setter - def layers(self, value: List[MapLayer]): - self.__layers = value - - # initial_center - @property - def initial_center(self) -> Optional[MapLatitudeLongitude]: - return self.__initial_center - - @initial_center.setter - def initial_center(self, value: Optional[MapLatitudeLongitude]): - self.__initial_center = value - - # initial_rotation - @property - def initial_rotation(self) -> OptionalNumber: - return self._get_attr("initialRotation", data_type="float") - - @initial_rotation.setter - def initial_rotation(self, value: OptionalNumber): - self._set_attr("initialRotation", value) - - # initial_zoom - @property - def initial_zoom(self) -> OptionalNumber: - return self._get_attr("initialZoom", data_type="float") - - @initial_zoom.setter - def initial_zoom(self, value: OptionalNumber): - self._set_attr("initialZoom", value) - - # interaction_configuration - @property - def interaction_configuration(self) -> Optional[MapInteractionConfiguration]: - return self.__interaction_configuration - - @interaction_configuration.setter - def interaction_configuration(self, value: Optional[MapInteractionConfiguration]): - self.__interaction_configuration = value - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # keep_alive - @property - def keep_alive(self) -> Optional[bool]: - return self._get_attr("keepAlive", data_type="bool") - - @keep_alive.setter - def keep_alive(self, value: Optional[bool]): - self._set_attr("keepAlive", value) - - # max_zoom - @property - def max_zoom(self) -> OptionalNumber: - return self._get_attr("maxZoom", data_type="float") - - @max_zoom.setter - def max_zoom(self, value: OptionalNumber): - self._set_attr("maxZoom", value) - - # min_zoom - @property - def min_zoom(self) -> OptionalNumber: - return self._get_attr("minZoom", data_type="float") - - @min_zoom.setter - def min_zoom(self, value: OptionalNumber): - self._set_attr("minZoom", value) - - # animation_curve - @property - def animation_curve(self) -> Optional[AnimationCurve]: - return self.__animation_curve - - @animation_curve.setter - def animation_curve(self, value: Optional[AnimationCurve]): - self.__animation_curve = value - self._set_enum_attr("animationCurve", value, AnimationCurve) - - # animation_duration - @property - def animation_duration(self) -> Optional[DurationValue]: - return self.__animation_duration - - @animation_duration.setter - def animation_duration(self, value: Optional[DurationValue]): - self.__animation_duration = value - - # on_tap - @property - def on_tap(self) -> OptionalEventCallable["MapTapEvent"]: - return self.__on_tap.handler - - @on_tap.setter - def on_tap(self, handler: OptionalEventCallable["MapTapEvent"]): - self.__on_tap.handler = handler - self._set_attr("onTap", True if handler is not None else None) - - # on_hover - @property - def on_hover(self) -> OptionalEventCallable["MapHoverEvent"]: - return self.__on_hover.handler - - @on_hover.setter - def on_hover(self, handler: OptionalEventCallable["MapHoverEvent"]): - self.__on_hover.handler = handler - self._set_attr("onHover", True if handler is not None else None) - - # on_secondary_tap - @property - def on_secondary_tap(self) -> OptionalEventCallable["MapTapEvent"]: - return self.__on_secondary_tap.handler - - @on_secondary_tap.setter - def on_secondary_tap(self, handler: OptionalEventCallable["MapTapEvent"]): - self.__on_secondary_tap.handler = handler - self._set_attr("onSecondaryTap", True if handler is not None else None) - - # on_long_press - @property - def on_long_press(self) -> OptionalEventCallable["MapTapEvent"]: - return self.__on_long_press.handler - - @on_long_press.setter - def on_long_press(self, handler: OptionalEventCallable["MapTapEvent"]): - self.__on_long_press.handler = handler - self._set_attr("onLongPress", True if handler is not None else None) - - # on_event - @property - def on_event(self) -> OptionalEventCallable["MapEvent"]: - return self.__on_event.handler - - @on_event.setter - def on_event(self, handler: OptionalEventCallable["MapEvent"]): - self.__on_event.handler = handler - self._set_attr("onEvent", True if handler is not None else None) - - # on_init - @property - def on_init(self) -> OptionalControlEventCallable: - return self._get_event_handler("init") - - @on_init.setter - def on_init(self, handler: OptionalControlEventCallable): - self._add_event_handler("init", handler) - self._set_attr("onInit", True if handler is not None else None) - - # on_position_change - @property - def on_position_change(self) -> OptionalEventCallable["MapPositionChangeEvent"]: - return self.__on_position_change.handler - - @on_position_change.setter - def on_position_change( - self, handler: OptionalEventCallable["MapPositionChangeEvent"] - ): - self.__on_position_change.handler = handler - self._set_attr("onPositionChange", True if handler is not None else None) - - # on_pointer_down - @property - def on_pointer_down(self) -> OptionalEventCallable["MapPointerEvent"]: - return self.__on_pointer_down.handler - - @on_pointer_down.setter - def on_pointer_down(self, handler: OptionalEventCallable["MapPointerEvent"]): - self.__on_pointer_down.handler = handler - self._set_attr("onPointerDown", True if handler is not None else None) - - # on_pointer_cancel - @property - def on_pointer_cancel(self) -> OptionalEventCallable["MapPointerEvent"]: - return self.__on_pointer_cancel.handler - - @on_pointer_cancel.setter - def on_pointer_cancel(self, handler: OptionalEventCallable["MapPointerEvent"]): - self.__on_pointer_cancel.handler = handler - self._set_attr("onPointerCancel", True if handler is not None else None) - - # on_pointer_up - @property - def on_pointer_up(self) -> OptionalEventCallable["MapPointerEvent"]: - return self.__on_pointer_up.handler - - @on_pointer_up.setter - def on_pointer_up(self, handler: OptionalEventCallable["MapPointerEvent"]): - self.__on_pointer_up.handler = handler - self._set_attr("onPointerUp", True if handler is not None else None) - - -class MapEventSource(Enum): - MAP_CONTROLLER = "mapController" - TAP = "tap" - SECONDARY_TAP = "secondaryTap" - LONG_PRESS = "longPress" - DOUBLE_TAP = "doubleTap" - DOUBLE_TAP_HOLD = "doubleTapHold" - DRAG_START = "dragStart" - ON_DRAG = "onDrag" - DRAG_END = "dragEnd" - MULTI_FINGER_GESTURE_START = "multiFingerGestureStart" - ON_MULTI_FINGER = "onMultiFinger" - MULTI_FINGER_GESTURE_END = "multiFingerEnd" - FLING_ANIMATION_CONTROLLER = "flingAnimationController" - DOUBLE_TAP_ZOOM_ANIMATION_CONTROLLER = "doubleTapZoomAnimationController" - INTERACTIVE_FLAGS_CHANGED = "interactiveFlagsChanged" - FIT_CAMERA = "fitCamera" - CUSTOM = "custom" - SCROLL_WHEEL = "scrollWheel" - NON_ROTATED_SIZE_CHANGE = "nonRotatedSizeChange" - CURSOR_KEYBOARD_ROTATION = "cursorKeyboardRotation" - - -class MapTapEvent(ControlEvent): - def __init__(self, e: ControlEvent) -> None: - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.local_x: Optional[float] = d.get("lx") - self.local_y: Optional[float] = d.get("ly") - self.global_x: float = d.get("gx") - self.global_y: float = d.get("gy") - self.coordinates: MapLatitudeLongitude = MapLatitudeLongitude( - d.get("lat"), d.get("long") - ) - - -class MapHoverEvent(ControlEvent): - def __init__(self, e: ControlEvent) -> None: - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.local_x: Optional[float] = d.get("lx") - self.local_y: Optional[float] = d.get("ly") - self.global_x: float = d.get("gx") - self.global_y: float = d.get("gy") - self.device_type: PointerDeviceType = PointerDeviceType(d.get("kind")) - self.coordinates: MapLatitudeLongitude = MapLatitudeLongitude( - d.get("lat"), d.get("long") - ) - - -class MapPositionChangeEvent(ControlEvent): - def __init__(self, e: ControlEvent) -> None: - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.min_zoom: Optional[float] = d.get("min_zoom") - self.max_zoom: Optional[float] = d.get("max_zoom") - self.rotation: float = d.get("rot") - self.coordinates: MapLatitudeLongitude = MapLatitudeLongitude( - d.get("lat"), d.get("long") - ) - - -class MapPointerEvent(ControlEvent): - def __init__(self, e: ControlEvent) -> None: - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.device_type: PointerDeviceType = PointerDeviceType(d.get("kind")) - self.global_y: float = d.get("gy") - self.global_x: float = d.get("gx") - self.coordinates: MapLatitudeLongitude = MapLatitudeLongitude( - d.get("lat"), d.get("long") - ) - - -class MapEvent(ControlEvent): - def __init__(self, e: ControlEvent) -> None: - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.source: MapEventSource = MapEventSource(d.get("src")) - self.center: MapLatitudeLongitude = MapLatitudeLongitude( - d.get("c_lat"), d.get("c_long") - ) - self.zoom: float = d.get("zoom") - self.min_zoom: float = d.get("min_zoom") - self.max_zoom: float = d.get("max_zoom") - self.rotation: float = d.get("rot") diff --git a/sdk/python/packages/flet/src/flet/core/map/map_layer.py b/sdk/python/packages/flet/src/flet/core/map/map_layer.py deleted file mode 100644 index 34f912fa2..000000000 --- a/sdk/python/packages/flet/src/flet/core/map/map_layer.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Any, Optional - -from flet.core.control import Control -from flet.core.ref import Ref - - -class MapLayer(Control): - """ - Abstract class for all map layers. - """ - - def __init__( - self, - # - # Control - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - visible=visible, - data=data, - ) diff --git a/sdk/python/packages/flet/src/flet/core/map/marker_layer.py b/sdk/python/packages/flet/src/flet/core/map/marker_layer.py deleted file mode 100644 index 20e2d1ca9..000000000 --- a/sdk/python/packages/flet/src/flet/core/map/marker_layer.py +++ /dev/null @@ -1,183 +0,0 @@ -from typing import Any, List, Optional - -from flet.core.alignment import Alignment -from flet.core.control import Control, OptionalNumber -from flet.core.map.map import MapLatitudeLongitude -from flet.core.map.map_layer import MapLayer -from flet.core.ref import Ref - - -class Marker(Control): - """ - A marker displayed on the Map at the specified location through the MarkerLayer. - - ----- - - Online docs: https://flet.dev/docs/controls/mapmarker - """ - - def __init__( - self, - content: Control, - coordinates: MapLatitudeLongitude, - rotate: Optional[bool] = None, - height: OptionalNumber = None, - width: OptionalNumber = None, - alignment: Optional[Alignment] = None, - # - # Control - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - visible=visible, - data=data, - ) - - self.content = content - self.coordinates = coordinates - self.rotate = rotate - self.height = height - self.width = width - self.alignment = alignment - - def _get_control_name(self): - return "map_marker" - - def _get_children(self): - return [self.__content] - - def before_update(self): - super().before_update() - self._set_attr_json("alignment", self.__alignment) - self._set_attr_json("coordinates", self.__coordinates) - - # content - @property - def content(self) -> Optional[Alignment]: - return self.__content - - @content.setter - def content(self, value: Optional[Alignment]): - self.__content = value - - # rotate - @property - def rotate(self) -> bool: - return self._get_attr("rotate", data_type="bool", def_value=False) - - @rotate.setter - def rotate(self, value: Optional[bool]): - self._set_attr("rotate", value) - - # height - @property - def height(self) -> float: - return self._get_attr("height", data_type="float", def_value=30.0) - - @height.setter - def height(self, value: OptionalNumber): - assert value is None or value >= 0, "height cannot be negative" - self._set_attr("height", value) - - # width - @property - def width(self) -> float: - return self._get_attr("width", data_type="float", def_value=30.0) - - @width.setter - def width(self, value: OptionalNumber): - assert value is None or value >= 0, "width cannot be negative" - self._set_attr("width", value) - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value - - # coordinates - @property - def coordinates(self) -> MapLatitudeLongitude: - return self.__coordinates - - @coordinates.setter - def coordinates(self, value: MapLatitudeLongitude): - self.__coordinates = value - - -class MarkerLayer(MapLayer): - """ - A layer to display Markers. - - ----- - - Online docs: https://flet.dev/docs/controls/mapmarkerlayer - """ - - def __init__( - self, - markers: List[Marker], - alignment: Optional[Alignment] = None, - rotate: Optional[bool] = None, - # - # MapLayer - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - MapLayer.__init__( - self, - ref=ref, - visible=visible, - data=data, - ) - - self.markers = markers - self.alignment = alignment - self.rotate = rotate - - def _get_control_name(self): - return "map_marker_layer" - - def _get_children(self): - return self.__markers - - def before_update(self): - super().before_update() - self._set_attr_json("alignment", self.__alignment) - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value - - # markers - @property - def markers(self) -> List[Marker]: - return self.__markers - - @markers.setter - def markers(self, value: List[Marker]): - self.__markers = value - - # rotate - @property - def rotate(self) -> bool: - return self._get_attr("rotate", data_type="bool", def_value=False) - - @rotate.setter - def rotate(self, value: Optional[bool]): - self._set_attr("rotate", value) diff --git a/sdk/python/packages/flet/src/flet/core/map/polygon_layer.py b/sdk/python/packages/flet/src/flet/core/map/polygon_layer.py deleted file mode 100644 index 6d5034b5c..000000000 --- a/sdk/python/packages/flet/src/flet/core/map/polygon_layer.py +++ /dev/null @@ -1,262 +0,0 @@ -from typing import Any, List, Optional - -from flet.core.control import Control, OptionalNumber -from flet.core.map.map import MapLatitudeLongitude -from flet.core.map.map_layer import MapLayer -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.types import ColorEnums, ColorValue, StrokeCap, StrokeJoin - - -class PolygonMarker(Control): - """ - A marker for the PolygonLayer. - - ----- - - Online docs: https://flet.dev/docs/controls/mappolygonmarker - """ - - def __init__( - self, - coordinates: List[MapLatitudeLongitude], - label: Optional[str] = None, - label_text_style: Optional[TextStyle] = None, - border_color: Optional[ColorValue] = None, - color: Optional[ColorValue] = None, - border_stroke_width: OptionalNumber = None, - disable_holes_border: Optional[bool] = None, - rotate_label: Optional[bool] = None, - stroke_cap: Optional[StrokeCap] = None, - stroke_join: Optional[StrokeJoin] = None, - # - # Control - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - visible=visible, - data=data, - ) - - self.coordinates = coordinates - self.label = label - self.label_text_style = label_text_style - self.border_color = border_color - self.color = color - self.border_stroke_width = border_stroke_width - self.disable_holes_border = disable_holes_border - self.rotate_label = rotate_label - self.stroke_cap = stroke_cap - self.stroke_join = stroke_join - - def _get_control_name(self): - return "map_polygon_marker" - - def before_update(self): - super().before_update() - self._set_attr_json("coordinates", self.__coordinates) - if isinstance(self.__label_text_style, TextStyle): - self._set_attr_json("labelTextStyle", self.__label_text_style) - - # stroke_cap - @property - def stroke_cap(self) -> Optional[StrokeCap]: - return self.__stroke_cap - - @stroke_cap.setter - def stroke_cap(self, value: Optional[StrokeCap]): - self.__stroke_cap = value - self._set_enum_attr("strokeCap", value, StrokeCap) - - # stroke_join - @property - def stroke_join(self) -> Optional[StrokeJoin]: - return self.__stroke_join - - @stroke_join.setter - def stroke_join(self, value: Optional[StrokeJoin]): - self.__stroke_join = value - self._set_enum_attr("strokeJoin", value, StrokeJoin) - - # label_text_style - @property - def label_text_style(self) -> Optional[TextStyle]: - return self.__label_text_style - - @label_text_style.setter - def label_text_style(self, value: Optional[TextStyle]): - self.__label_text_style = value - - # rotate_label - @property - def rotate_label(self) -> bool: - return self._get_attr("rotateLabel", data_type="bool", def_value=False) - - @rotate_label.setter - def rotate_label(self, value: Optional[bool]): - self._set_attr("rotateLabel", value) - - # label - @property - def label(self) -> Optional[str]: - return self._get_attr("label") - - @label.setter - def label(self, value: Optional[str]): - self._set_attr("label", value) - - # disable_holes_border - @property - def disable_holes_border(self) -> bool: - return self._get_attr("disableHolesBorder", data_type="bool", def_value=False) - - @disable_holes_border.setter - def disable_holes_border(self, value: Optional[bool]): - self._set_attr("disableHolesBorder", value) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # border_color - @property - def border_color(self) -> Optional[ColorValue]: - return self.__border_color - - @border_color.setter - def border_color(self, value: Optional[ColorValue]): - self.__border_color = value - self._set_enum_attr("borderColor", value, ColorEnums) - - # border_stroke_width - @property - def border_stroke_width(self) -> float: - return self._get_attr("borderStrokeWidth", data_type="float", def_value=0.0) - - @border_stroke_width.setter - def border_stroke_width(self, value: OptionalNumber): - assert value is None or value >= 0, "border_stroke_width cannot be negative" - self._set_attr("borderStrokeWidth", value) - - # coordinates - @property - def coordinates(self) -> List[MapLatitudeLongitude]: - return self.__coordinates - - @coordinates.setter - def coordinates(self, value: List[MapLatitudeLongitude]): - self.__coordinates = value - - -class PolygonLayer(MapLayer): - """ - A layer to display PolygonMarkers. - - ----- - - Online docs: https://flet.dev/docs/controls/mappolygonlayer - """ - - def __init__( - self, - polygons: List[PolygonMarker], - polygon_culling: Optional[bool] = None, - polygon_labels: Optional[bool] = None, - draw_labels_last: Optional[bool] = None, - simplification_tolerance: OptionalNumber = None, - use_alternative_rendering: Optional[bool] = None, - # - # MapLayer - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - MapLayer.__init__( - self, - ref=ref, - visible=visible, - data=data, - ) - - self.polygons = polygons - self.polygon_culling = polygon_culling - self.polygon_labels = polygon_labels - self.draw_labels_last = draw_labels_last - self.simplification_tolerance = simplification_tolerance - self.use_alternative_rendering = use_alternative_rendering - - def _get_control_name(self): - return "map_polygon_layer" - - def _get_children(self): - return self.__polygons - - # polygons - @property - def polygons(self) -> List[PolygonMarker]: - return self.__polygons - - @polygons.setter - def polygons(self, value: List[PolygonMarker]): - self.__polygons = value - - # polygon_culling - @property - def polygon_culling(self) -> bool: - return self._get_attr("polygonCulling", data_type="bool", def_value=False) - - @polygon_culling.setter - def polygon_culling(self, value: Optional[bool]): - self._set_attr("polygonCulling", value) - - # use_alternative_rendering - @property - def use_alternative_rendering(self) -> bool: - return self._get_attr( - "useAlternativeRendering", data_type="bool", def_value=False - ) - - @use_alternative_rendering.setter - def use_alternative_rendering(self, value: Optional[bool]): - self._set_attr("useAlternativeRendering", value) - - # polygon_labels - @property - def polygon_labels(self) -> bool: - return self._get_attr("polygonLabels", data_type="bool", def_value=True) - - @polygon_labels.setter - def polygon_labels(self, value: Optional[bool]): - self._set_attr("polygonLabels", value) - - # simplification_tolerance - @property - def simplification_tolerance(self) -> float: - return self._get_attr( - "simplificationTolerance", data_type="float", def_value=0.5 - ) - - @simplification_tolerance.setter - def simplification_tolerance(self, value: OptionalNumber): - self._set_attr("simplificationTolerance", value) - - # draw_labels_last - @property - def draw_labels_last(self) -> bool: - return self._get_attr("drawLabelsLast", data_type="bool", def_value=False) - - @draw_labels_last.setter - def draw_labels_last(self, value: Optional[bool]): - self._set_attr("drawLabelsLast", value) diff --git a/sdk/python/packages/flet/src/flet/core/map/polyline_layer.py b/sdk/python/packages/flet/src/flet/core/map/polyline_layer.py deleted file mode 100644 index 9bc4698be..000000000 --- a/sdk/python/packages/flet/src/flet/core/map/polyline_layer.py +++ /dev/null @@ -1,292 +0,0 @@ -from dataclasses import dataclass -from enum import Enum -from typing import Any, List, Optional, Union - -from flet.core.control import Control, OptionalNumber -from flet.core.map.map import MapLatitudeLongitude -from flet.core.map.map_layer import MapLayer -from flet.core.ref import Ref -from flet.core.types import ColorEnums, ColorValue, StrokeCap, StrokeJoin - - -class PatternFit(Enum): - SCALE_DOWN = "scaleDown" - SCALE_UP = "scaleUp" - APPEND_DOT = "appendDot" - EXTEND_FINAL_DASH = "extendFinalDash" - - -@dataclass -class StrokePattern: - pass - - -@dataclass -class SolidStrokePattern(StrokePattern): - def __post_init__(self): - self.type = "solid" - - -@dataclass -class DashedStrokePattern(StrokePattern): - segments: Optional[List[Union[float, int]]] = None - pattern_fit: Optional[PatternFit] = PatternFit.SCALE_UP - - def __post_init__(self): - self.type = "dashed" - - -@dataclass -class DottedStrokePattern(StrokePattern): - spacing_factor: OptionalNumber = None - pattern_fit: Optional[PatternFit] = PatternFit.SCALE_UP - - def __post_init__(self): - self.type = "dotted" - - -class PolylineMarker(Control): - """ - A marker for the PolylineLayer. - - ----- - - Online docs: https://flet.dev/docs/controls/mappolylinemarker - """ - - def __init__( - self, - coordinates: List[MapLatitudeLongitude], - colors_stop: Optional[List[Union[float, int]]] = None, - gradient_colors: Optional[List[str]] = None, - border_color: Optional[ColorValue] = None, - color: Optional[ColorValue] = None, - stroke_width: OptionalNumber = None, - border_stroke_width: OptionalNumber = None, - use_stroke_width_in_meter: Optional[bool] = None, - stroke_pattern: Optional[StrokePattern] = None, - stroke_cap: Optional[StrokeCap] = None, - stroke_join: Optional[StrokeJoin] = None, - # - # Control - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - visible=visible, - data=data, - ) - - self.coordinates = coordinates - self.border_color = border_color - self.color = color - self.border_stroke_width = border_stroke_width - self.stroke_width = stroke_width - self.stroke_cap = stroke_cap - self.stroke_join = stroke_join - self.colors_stop = colors_stop - self.gradient_colors = gradient_colors - self.use_stroke_width_in_meter = use_stroke_width_in_meter - self.stroke_pattern = stroke_pattern - - def _get_control_name(self): - return "map_polyline_marker" - - def before_update(self): - super().before_update() - self._set_attr_json("coordinates", self.__coordinates) - if isinstance(self.__colors_stop, list): - self._set_attr_json("colorsStop", self.__colors_stop) - if isinstance(self.__gradient_colors, list): - self._set_attr_json("gradientColors", self.__gradient_colors) - self._set_attr_json("strokePattern", self.__stroke_pattern) - - # stroke_cap - @property - def stroke_cap(self) -> Optional[StrokeCap]: - return self.__stroke_cap - - @stroke_cap.setter - def stroke_cap(self, value: Optional[StrokeCap]): - self.__stroke_cap = value - self._set_enum_attr("strokeCap", value, StrokeCap) - - # gradient_colors - @property - def gradient_colors(self) -> Optional[List[str]]: - return self.__gradient_colors - - @gradient_colors.setter - def gradient_colors(self, value: Optional[List[str]]): - self.__gradient_colors = value - - # stroke_pattern - @property - def stroke_pattern(self) -> Optional[StrokePattern]: - return self.__stroke_pattern - - @stroke_pattern.setter - def stroke_pattern(self, value: Optional[StrokePattern]): - self.__stroke_pattern = value - - # colors_stop - @property - def colors_stop(self) -> Optional[List[Union[float, int]]]: - return self.__colors_stop - - @colors_stop.setter - def colors_stop(self, value: Optional[List[Union[float, int]]]): - self.__colors_stop = value - - # stroke_join - @property - def stroke_join(self) -> Optional[StrokeJoin]: - return self.__stroke_join - - @stroke_join.setter - def stroke_join(self, value: Optional[StrokeJoin]): - self.__stroke_join = value - self._set_enum_attr("strokeJoin", value, StrokeJoin) - - # use_stroke_width_in_meter - @property - def use_stroke_width_in_meter(self) -> bool: - return self._get_attr( - "useStrokeWidthInMeter", data_type="bool", def_value=False - ) - - @use_stroke_width_in_meter.setter - def use_stroke_width_in_meter(self, value: Optional[bool]): - self._set_attr("useStrokeWidthInMeter", value) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # border_color - @property - def border_color(self) -> Optional[ColorValue]: - return self.__border_color - - @border_color.setter - def border_color(self, value: Optional[ColorValue]): - self.__border_color = value - self._set_enum_attr("borderColor", value, ColorEnums) - - # border_stroke_width - @property - def border_stroke_width(self) -> float: - return self._get_attr("borderStrokeWidth", data_type="float", def_value=0.0) - - @border_stroke_width.setter - def border_stroke_width(self, value: OptionalNumber): - assert value is None or value >= 0, "border_stroke_width cannot be negative" - self._set_attr("borderStrokeWidth", value) - - # stroke_width - @property - def stroke_width(self) -> float: - return self._get_attr("strokeWidth", data_type="float", def_value=1.0) - - @stroke_width.setter - def stroke_width(self, value: OptionalNumber): - assert value is None or value >= 0, "stroke_width cannot be negative" - self._set_attr("strokeWidth", value) - - # coordinates - @property - def coordinates(self) -> List[MapLatitudeLongitude]: - return self.__coordinates - - @coordinates.setter - def coordinates(self, value: List[MapLatitudeLongitude]): - self.__coordinates = value - - -class PolylineLayer(MapLayer): - """ - A layer to display PolylineMarkers. - - ----- - - Online docs: https://flet.dev/docs/controls/mappolylinelayer - """ - - def __init__( - self, - polylines: List[PolylineMarker], - culling_margin: OptionalNumber = None, - min_hittable_radius: OptionalNumber = None, - simplify_tolerance: OptionalNumber = None, - # - # MapLayer - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - MapLayer.__init__( - self, - ref=ref, - visible=visible, - data=data, - ) - - self.polylines = polylines - self.culling_margin = culling_margin - self.min_hittable_radius = min_hittable_radius - self.simplify_tolerance = simplify_tolerance - - def _get_control_name(self): - return "map_polyline_layer" - - def _get_children(self): - return self.__polylines - - # polylines - @property - def polylines(self) -> List[PolylineMarker]: - return self.__polylines - - @polylines.setter - def polylines(self, value: List[PolylineMarker]): - self.__polylines = value - - # culling_margin - @property - def culling_margin(self) -> float: - return self._get_attr("cullingMargin", data_type="float", def_value=10.0) - - @culling_margin.setter - def culling_margin(self, value: OptionalNumber): - self._set_attr("cullingMargin", value) - - # simplification_tolerance - @property - def simplification_tolerance(self) -> float: - return self._get_attr( - "simplificationTolerance", data_type="float", def_value=0.4 - ) - - @simplification_tolerance.setter - def simplification_tolerance(self, value: OptionalNumber): - self._set_attr("simplificationTolerance", value) - - # min_hittable_radius - @property - def min_hittable_radius(self) -> float: - return self._get_attr("minHittableRadius", data_type="float", def_value=10.0) - - @min_hittable_radius.setter - def min_hittable_radius(self, value: OptionalNumber): - self._set_attr("minHittableRadius", value) diff --git a/sdk/python/packages/flet/src/flet/core/map/rich_attribution.py b/sdk/python/packages/flet/src/flet/core/map/rich_attribution.py deleted file mode 100644 index c65ff12a6..000000000 --- a/sdk/python/packages/flet/src/flet/core/map/rich_attribution.py +++ /dev/null @@ -1,141 +0,0 @@ -from enum import Enum -from typing import Any, List, Optional - -from flet.core.border_radius import BorderRadius -from flet.core.control import OptionalNumber -from flet.core.map.map_layer import MapLayer -from flet.core.map.text_source_attribution import TextSourceAttribution -from flet.core.ref import Ref -from flet.core.types import ColorEnums, ColorValue - - -class AttributionAlignment(Enum): - BOTTOM_LEFT = "bottomLeft" - BOTTOM_RIGHT = "bottomRight" - - -class RichAttribution(MapLayer): - """ - An animated and interactive attribution layer that supports both logos/images and text - (displayed in a popup controlled by an icon button adjacent to the logos). - - ----- - - Online docs: https://flet.dev/docs/controls/maprichattribution - """ - - def __init__( - self, - attributions: List[TextSourceAttribution], - alignment: Optional[AttributionAlignment] = None, - popup_bgcolor: Optional[ColorValue] = None, - popup_border_radius: Optional[BorderRadius] = None, - popup_initial_display_duration: Optional[int] = None, - permanent_height: OptionalNumber = None, - show_flutter_map_attribution: Optional[bool] = None, - # - # MapLayer - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - MapLayer.__init__( - self, - ref=ref, - visible=visible, - data=data, - ) - - self.attributions = attributions - self.alignment = alignment - self.popup_bgcolor = popup_bgcolor - self.permanent_height = permanent_height - self.show_flutter_map_attribution = show_flutter_map_attribution - self.popup_border_radius = popup_border_radius - self.popup_initial_display_duration = popup_initial_display_duration - - def _get_control_name(self): - return "map_rich_attribution" - - def _get_children(self): - return self.attributions - - def before_update(self): - super().before_update() - self._set_attr_json("popupBorderRadius", self.__popup_border_radius) - self._set_attr_json("alignment", self.__alignment) - - # permanent_height - @property - def permanent_height(self) -> float: - return self._get_attr("permanentHeight", data_type="float", def_value=24.0) - - @permanent_height.setter - def permanent_height(self, value: OptionalNumber): - assert value is None or value >= 0, "permanent_height cannot be negative" - self._set_attr("permanentHeight", value) - - # popup_initial_display_duration - @property - def popup_initial_display_duration(self) -> int: - return self._get_attr( - "popupInitialDisplayDuration", data_type="int", def_value=0 - ) - - @popup_initial_display_duration.setter - def popup_initial_display_duration(self, value: Optional[int]): - assert ( - value is None or value >= 0 - ), "popup_initial_display_duration cannot be negative" - self._set_attr("popupInitialDisplayDuration", value) - - # popup_border_radius - @property - def popup_border_radius(self) -> Optional[BorderRadius]: - return self.__popup_border_radius - - @popup_border_radius.setter - def popup_border_radius(self, value: Optional[BorderRadius]): - self.__popup_border_radius = value - - # alignment - @property - def alignment(self) -> Optional[AttributionAlignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[AttributionAlignment]): - self.__alignment = value - self._set_enum_attr("alignment", value, AttributionAlignment) - - # show_flutter_map_attribution - @property - def show_flutter_map_attribution(self) -> bool: - return self._get_attr( - "showFlutterMapAttribution", data_type="bool", def_value=True - ) - - @show_flutter_map_attribution.setter - def show_flutter_map_attribution(self, value: Optional[bool]): - self._set_attr("showFlutterMapAttribution", value) - - # popup_bgcolor - @property - def popup_bgcolor(self) -> Optional[str]: - return self.__popup_bgcolor - - @popup_bgcolor.setter - def popup_bgcolor(self, value: Optional[str]): - self.__popup_bgcolor = value - self._set_enum_attr("popupBgcolor", value, ColorEnums) - - # attributions - @property - def attributions(self) -> List[TextSourceAttribution]: - return self.__attributions - - @attributions.setter - def attributions(self, value: List[TextSourceAttribution]): - self.__attributions = value diff --git a/sdk/python/packages/flet/src/flet/core/map/simple_attribution.py b/sdk/python/packages/flet/src/flet/core/map/simple_attribution.py deleted file mode 100644 index b61f18c88..000000000 --- a/sdk/python/packages/flet/src/flet/core/map/simple_attribution.py +++ /dev/null @@ -1,86 +0,0 @@ -from typing import Any, Optional - -from flet.core.alignment import Alignment -from flet.core.map.map_layer import MapLayer -from flet.core.ref import Ref -from flet.core.types import ColorEnums, ColorValue, OptionalControlEventCallable - - -class SimpleAttribution(MapLayer): - """ - A simple attribution layer displayed on the Map. - - ----- - - Online docs: https://flet.dev/docs/controls/mapsimpleattribution - """ - - def __init__( - self, - text: str, - alignment: Optional[Alignment] = None, - bgcolor: Optional[ColorValue] = None, - on_click: OptionalControlEventCallable = None, - # - # MapLayer - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - MapLayer.__init__( - self, - ref=ref, - visible=visible, - data=data, - ) - - self.text = text - self.alignment = alignment - self.bgcolor = bgcolor - self.on_click = on_click - - def _get_control_name(self): - return "map_simple_attribution" - - def before_update(self): - super().before_update() - self._set_attr_json("alignment", self.__alignment) - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # text - @property - def text(self) -> str: - return self._get_attr("text") - - @text.setter - def text(self, value: str): - self._set_attr("text", value) - - # on_click - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) diff --git a/sdk/python/packages/flet/src/flet/core/map/text_source_attribution.py b/sdk/python/packages/flet/src/flet/core/map/text_source_attribution.py deleted file mode 100644 index e702035fa..000000000 --- a/sdk/python/packages/flet/src/flet/core/map/text_source_attribution.py +++ /dev/null @@ -1,87 +0,0 @@ -from typing import Any, Optional - -from flet.core.control import Control -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.types import OptionalControlEventCallable - - -class TextSourceAttribution(Control): - """ - A text source attribution displayed on the Map. - For it to be displayed, it should be part of a RichAttribution.attributions list. - - ----- - - Online docs: https://flet.dev/docs/controls/maptextsourceattribution - """ - - def __init__( - self, - text: str, - text_style: Optional[TextStyle] = None, - prepend_copyright: Optional[bool] = None, - on_click: OptionalControlEventCallable = None, - # - # Control - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - visible=visible, - data=data, - ) - - self.text = text - self.text_style = text_style - self.prepend_copyright = prepend_copyright - self.on_click = on_click - - def _get_control_name(self): - return "map_text_source_attribution" - - def before_update(self): - super().before_update() - if isinstance(self.__text_style, TextStyle): - self._set_attr_json("textStyle", self.__text_style) - - # text_style - @property - def text_style(self) -> Optional[TextStyle]: - return self.__text_style - - @text_style.setter - def text_style(self, value: Optional[TextStyle]): - self.__text_style = value - - # prepend_copyright - @property - def prepend_copyright(self) -> bool: - return self._get_attr("prependCopyright", data_type="bool", def_value=True) - - @prepend_copyright.setter - def prepend_copyright(self, value: Optional[bool]): - self._set_attr("prependCopyright", value) - - # text - @property - def text(self) -> str: - return self._get_attr("text") - - @text.setter - def text(self, value: str): - self._set_attr("text", value) - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) diff --git a/sdk/python/packages/flet/src/flet/core/map/tile_layer.py b/sdk/python/packages/flet/src/flet/core/map/tile_layer.py deleted file mode 100644 index a6153cf25..000000000 --- a/sdk/python/packages/flet/src/flet/core/map/tile_layer.py +++ /dev/null @@ -1,278 +0,0 @@ -from enum import Enum -from typing import Any, Dict, List, Optional - -from flet.core.control import OptionalNumber -from flet.core.map.map import MapLatitudeLongitudeBounds -from flet.core.map.map_layer import MapLayer -from flet.core.ref import Ref -from flet.core.types import OptionalControlEventCallable - - -class MapTileLayerEvictErrorTileStrategy(Enum): - DISPOSE = "dispose" - NOT_VISIBLE = "notVisible" - NOT_VISIBLE_RESPECT_MARGIN = "notVisibleRespectMargin" - - -class TileLayer(MapLayer): - """ - The Map's main layer. - Displays square raster images in a continuous grid, sourced from the provided utl_template. - - ----- - - Online docs: https://flet.dev/docs/controls/maptilelayer - """ - - def __init__( - self, - url_template: str, - fallback_url: Optional[str] = None, - subdomains: Optional[List[str]] = None, - tile_bounds: Optional[MapLatitudeLongitudeBounds] = None, - tile_size: OptionalNumber = None, - min_native_zoom: Optional[int] = None, - max_native_zoom: Optional[int] = None, - zoom_reverse: Optional[bool] = None, - zoom_offset: OptionalNumber = None, - keep_buffer: Optional[int] = None, - pan_buffer: Optional[int] = None, - enable_tms: Optional[bool] = None, - keep_alive: Optional[bool] = None, - enable_retina_mode: Optional[bool] = None, - additional_options: Optional[Dict[str, str]] = None, - max_zoom: OptionalNumber = None, - min_zoom: OptionalNumber = None, - error_image_src: Optional[str] = None, - evict_error_tile_strategy: Optional[MapTileLayerEvictErrorTileStrategy] = None, - on_image_error: OptionalControlEventCallable = None, - # - # MapLayer - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - MapLayer.__init__( - self, - ref=ref, - visible=visible, - data=data, - ) - - self.url_template = url_template - self.fallback_url = fallback_url - self.tile_size = tile_size - self.min_native_zoom = min_native_zoom - self.max_native_zoom = max_native_zoom - self.zoom_reverse = zoom_reverse - self.zoom_offset = zoom_offset - self.keep_buffer = keep_buffer - self.pan_buffer = pan_buffer - self.enable_tms = enable_tms - self.keep_alive = keep_alive - self.max_zoom = max_zoom - self.min_zoom = min_zoom - self.error_image_src = error_image_src - self.enable_retina_mode = enable_retina_mode - self.on_image_error = on_image_error - self.tile_bounds = tile_bounds - self.evict_error_tile_strategy = evict_error_tile_strategy - self.subdomains = subdomains - self.additional_options = additional_options - - def _get_control_name(self): - return "map_tile_layer" - - def before_update(self): - super().before_update() - assert self.url_template, "url_template is required" - if isinstance(self.__tile_bounds, MapLatitudeLongitudeBounds): - self._set_attr_json("tileBounds", self.__tile_bounds) - if isinstance(self.__subdomains, list): - self._set_attr_json("subdomains", self.__subdomains) - if isinstance(self.__additional_options, dict): - self._set_attr_json("additionalOptions", self.__additional_options) - - # url_template - @property - def url_template(self) -> str: - return self._get_attr("urlTemplate") - - @url_template.setter - def url_template(self, value: str): - self._set_attr("urlTemplate", value) - - # fallback_url - @property - def fallback_url(self) -> Optional[str]: - return self._get_attr("fallbackUrl") - - @fallback_url.setter - def fallback_url(self, value: Optional[str]): - self._set_attr("fallbackUrl", value) - - # subdomains - @property - def subdomains(self) -> Optional[List[str]]: - return self.__subdomains - - @subdomains.setter - def subdomains(self, value: Optional[List[str]]): - self.__subdomains = value - - # additional_options - @property - def additional_options(self) -> Optional[Dict[str, str]]: - return self.__additional_options - - @additional_options.setter - def additional_options(self, value: Optional[Dict[str, str]]): - self.__additional_options = value - - # tile_bounds - @property - def tile_bounds(self) -> Optional[MapLatitudeLongitudeBounds]: - return self.__tile_bounds - - @tile_bounds.setter - def tile_bounds(self, value: Optional[MapLatitudeLongitudeBounds]): - self.__tile_bounds = value - - # tile_size - @property - def tile_size(self) -> float: - return self._get_attr("tileSize", data_type="float", def_value=256.0) - - @tile_size.setter - def tile_size(self, value: OptionalNumber): - assert value is None or value >= 0, "tile_size cannot be negative" - self._set_attr("tileSize", value) - - # min_native_zoom - @property - def min_native_zoom(self) -> int: - return self._get_attr("minNativeZoom", data_type="int", def_value=0.0) - - @min_native_zoom.setter - def min_native_zoom(self, value: Optional[int]): - assert value is None or value >= 0, "min_native_zoom cannot be negative" - self._set_attr("minNativeZoom", value) - - # max_native_zoom - @property - def max_native_zoom(self) -> int: - return self._get_attr("maxNativeZoom", data_type="int", def_value=19) - - @max_native_zoom.setter - def max_native_zoom(self, value: Optional[int]): - assert value is None or value >= 0, "max_native_zoom cannot be negative" - self._set_attr("maxNativeZoom", value) - - # zoom_reverse - @property - def zoom_reverse(self) -> bool: - return self._get_attr("zoomReverse", data_type="bool", def_value=False) - - @zoom_reverse.setter - def zoom_reverse(self, value: Optional[bool]): - self._set_attr("zoomReverse", value) - - # zoom_offset - @property - def zoom_offset(self) -> float: - return self._get_attr("zoomOffset", data_type="float", def_value=0.0) - - @zoom_offset.setter - def zoom_offset(self, value: OptionalNumber): - assert value is None or value >= 0, "zoom_offset cannot be negative" - self._set_attr("zoomOffset", value) - - # keep_buffer - @property - def keep_buffer(self) -> int: - return self._get_attr("keepBuffer", data_type="int", def_value=2) - - @keep_buffer.setter - def keep_buffer(self, value: Optional[int]): - self._set_attr("keepBuffer", value) - - # pan_buffer - @property - def pan_buffer(self) -> int: - return self._get_attr("panBuffer", data_type="int", def_value=2) - - @pan_buffer.setter - def pan_buffer(self, value: Optional[int]): - self._set_attr("panBuffer", value) - - # enable_tms - @property - def enable_tms(self) -> bool: - return self._get_attr("enableTms", data_type="bool", def_value=False) - - @enable_tms.setter - def enable_tms(self, value: Optional[bool]): - self._set_attr("enableTms", value) - - # enable_retina_mode - @property - def enable_retina_mode(self) -> bool: - return self._get_attr("enableRetinaMode", data_type="bool", def_value=False) - - @enable_retina_mode.setter - def enable_retina_mode(self, value: Optional[bool]): - self._set_attr("enableRetinaMode", value) - - # max_zoom - @property - def max_zoom(self) -> float: - return self._get_attr("maxZoom", data_type="float", def_value=float("inf")) - - @max_zoom.setter - def max_zoom(self, value: OptionalNumber): - assert value is None or value >= 0, "max_zoom cannot be negative" - self._set_attr("maxZoom", value) - - # min_zoom - @property - def min_zoom(self) -> float: - return self._get_attr("minZoom", data_type="float", def_value=0.0) - - @min_zoom.setter - def min_zoom(self, value: OptionalNumber): - assert value is None or value >= 0, "min_zoom cannot be negative" - self._set_attr("minZoom", value) - - # error_image_src - @property - def error_image_src(self) -> Optional[str]: - return self._get_attr("errorImageSrc") - - @error_image_src.setter - def error_image_src(self, value: Optional[str]): - self._set_attr("errorImageSrc", value) - - # evict_error_tile_strategy - @property - def evict_error_tile_strategy(self) -> Optional[MapTileLayerEvictErrorTileStrategy]: - return self.__evict_error_tile_strategy - - @evict_error_tile_strategy.setter - def evict_error_tile_strategy( - self, value: Optional[MapTileLayerEvictErrorTileStrategy] - ): - self.__evict_error_tile_strategy = value - self._set_enum_attr( - "evictErrorTileStrategy", value, MapTileLayerEvictErrorTileStrategy - ) - - # on_image_error - @property - def on_image_error(self) -> OptionalControlEventCallable: - return self._get_event_handler("imageError") - - @on_image_error.setter - def on_image_error(self, handler: OptionalControlEventCallable): - self._add_event_handler("imageError", handler) diff --git a/sdk/python/packages/flet/src/flet/core/margin.py b/sdk/python/packages/flet/src/flet/core/margin.py deleted file mode 100644 index 867b783dc..000000000 --- a/sdk/python/packages/flet/src/flet/core/margin.py +++ /dev/null @@ -1,24 +0,0 @@ -import dataclasses -from typing import Union - - -@dataclasses.dataclass -class Margin: - left: Union[float, int] - top: Union[float, int] - right: Union[float, int] - bottom: Union[float, int] - - -def all(value: float) -> Margin: - return Margin(left=value, top=value, right=value, bottom=value) - - -def symmetric(vertical: float = 0, horizontal: float = 0) -> Margin: - return Margin(left=horizontal, top=vertical, right=horizontal, bottom=vertical) - - -def only( - left: float = 0, top: float = 0, right: float = 0, bottom: float = 0 -) -> Margin: - return Margin(left=left, top=top, right=right, bottom=bottom) diff --git a/sdk/python/packages/flet/src/flet/core/markdown.py b/sdk/python/packages/flet/src/flet/core/markdown.py deleted file mode 100644 index e46579a3f..000000000 --- a/sdk/python/packages/flet/src/flet/core/markdown.py +++ /dev/null @@ -1,512 +0,0 @@ -from dataclasses import dataclass -from enum import Enum -from typing import Any, Optional, Union, cast - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.box import BoxDecoration -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.text import TextSelectionChangeEvent -from flet.core.text_style import TextStyle -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - MainAxisAlignment, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, - TextAlign, -) - -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal - - -class MarkdownExtensionSet(Enum): - NONE = "none" - COMMON_MARK = "commonMark" - GITHUB_WEB = "gitHubWeb" - GITHUB_FLAVORED = "gitHubFlavored" - - -@dataclass -class MarkdownStyleSheet: - a_text_style: Optional[TextStyle] = None - p_text_style: Optional[TextStyle] = None - p_padding: Optional[PaddingValue] = None - code_text_style: Optional[TextStyle] = None - h1_text_style: Optional[TextStyle] = None - h1_padding: Optional[PaddingValue] = None - h2_text_style: Optional[TextStyle] = None - h2_padding: Optional[PaddingValue] = None - h3_text_style: Optional[TextStyle] = None - h3_padding: Optional[PaddingValue] = None - h4_text_style: Optional[TextStyle] = None - h4_padding: Optional[PaddingValue] = None - h5_text_style: Optional[TextStyle] = None - h5_padding: Optional[PaddingValue] = None - h6_text_style: Optional[TextStyle] = None - h6_padding: Optional[PaddingValue] = None - em_text_style: Optional[TextStyle] = None - strong_text_style: Optional[TextStyle] = None - del_text_style: Optional[TextStyle] = None - blockquote_text_style: Optional[TextStyle] = None - img_text_style: Optional[TextStyle] = None - checkbox_text_style: Optional[TextStyle] = None - block_spacing: OptionalNumber = None - list_indent: OptionalNumber = None - list_bullet_text_style: Optional[TextStyle] = None - list_bullet_padding: Optional[PaddingValue] = None - table_head_text_style: Optional[TextStyle] = None - table_body_text_style: Optional[TextStyle] = None - table_head_text_align: Optional[TextAlign] = None - table_padding: Optional[PaddingValue] = None - table_cells_padding: Optional[PaddingValue] = None - blockquote_padding: Optional[PaddingValue] = None - table_cells_decoration: Optional[BoxDecoration] = None - blockquote_decoration: Optional[BoxDecoration] = None - codeblock_padding: Optional[PaddingValue] = None - codeblock_decoration: Optional[BoxDecoration] = None - horizontal_rule_decoration: Optional[BoxDecoration] = None - blockquote_alignment: Optional[MainAxisAlignment] = None - codeblock_alignment: Optional[MainAxisAlignment] = None - h1_alignment: Optional[MainAxisAlignment] = None - h2_alignment: Optional[MainAxisAlignment] = None - h3_alignment: Optional[MainAxisAlignment] = None - h4_alignment: Optional[MainAxisAlignment] = None - h5_alignment: Optional[MainAxisAlignment] = None - h6_alignment: Optional[MainAxisAlignment] = None - text_alignment: Optional[MainAxisAlignment] = None - ordered_list_alignment: Optional[MainAxisAlignment] = None - unordered_list_alignment: Optional[MainAxisAlignment] = None - - -class MarkdownCodeTheme(Enum): - A11Y_DARK = "a11y-dark" - A11Y_LIGHT = "a11y-light" - AGATE = "agate" - AN_OLD_HOPE = "an-old-hope" - ANDROID_STUDIO = "androidstudio" - ARDUINO_LIGHT = "arduino-light" - ARTA = "arta" - ASCETIC = "ascetic" - ATELIER_CAVE_DARK = "atelier-cave-dark" - ATELIER_CAVE_LIGHT = "atelier-cave-light" - ATELIER_DUNE_DARK = "atelier-dune-dark" - ATELIER_DUNE_LIGHT = "atelier-dune-light" - ATELIER_ESTUARY_DARK = "atelier-estuary-dark" - ATELIER_ESTUARY_LIGHT = "atelier-estuary-light" - ATELIER_FOREST_DARK = "atelier-forest-dark" - ATELIER_FOREST_LIGHT = "atelier-forest-light" - ATELIER_HEATH_DARK = "atelier-heath-dark" - ATELIER_HEATH_LIGHT = "atelier-heath-light" - ATELIER_LAKESIDE_DARK = "atelier-lakeside-dark" - ATELIER_LAKESIDE_LIGHT = "atelier-lakeside-light" - ATELIER_PLATEAU_DARK = "atelier-plateau-dark" - ATELIER_PLATEAU_LIGHT = "atelier-plateau-light" - ATELIER_SAVANNA_DARK = "atelier-savanna-dark" - ATELIER_SAVANNA_LIGHT = "atelier-savanna-light" - ATELIER_SEASIDE_DARK = "atelier-seaside-dark" - ATELIER_SEASIDE_LIGHT = "atelier-seaside-light" - ATELIER_SULPHURPOOL_DARK = "atelier-sulphurpool-dark" - ATELIER_SULPHURPOOL_LIGHT = "atelier-sulphurpool-light" - ATOM_ONE_DARK_REASONABLE = "atom-one-dark-reasonable" - ATOM_ONE_DARK = "atom-one-dark" - ATOM_ONE_LIGHT = "atom-one-light" - BROWN_PAPER = "brown-paper" - CODEPEN_EMBED = "codepen-embed" - COLOR_BREWER = "color-brewer" - DARCULA = "darcula" - DARK = "dark" - DEFAULT = "default" - DOCCO = "docco" - DRAGULA = "dracula" - FAR = "far" - FOUNDATION = "foundation" - GITHUB_GIST = "github-gist" - GITHUB = "github" - GML = "gml" - GOOGLE_CODE = "googlecode" - GRADIENT_DARK = "gradient-dark" - GRAYSCALE = "grayscale" - GRUVBOX_DARK = "gruvbox-dark" - GRUVBOX_LIGHT = "gruvbox-light" - HOPSCOTCH = "hopscotch" - HYBRID = "hybrid" - IDEA = "idea" - IR_BLACK = "ir-black" - ISBL_EDITOR_DARK = "isbl-editor-dark" - ISBL_EDITOR_LIGHT = "isbl-editor-light" - KIMBIE_DARK = "kimbie.dark" - KIMBIE_LIGHT = "kimbie.light" - LIGHTFAIR = "lightfair" - MAGULA = "magula" - MONO_BLUE = "mono-blue" - MONOKAI_SUBLIME = "monokai-sublime" - MONOKAI = "monokai" - NIGHT_OWL = "night-owl" - NORD = "nord" - OBSIDIAN = "obsidian" - OCEAN = "ocean" - PARAISEO_DARK = "paraiso-dark" - PARAISEO_LIGHT = "paraiso-light" - POJOAQUE = "pojoaque" - PURE_BASIC = "purebasic" - QT_CREATOR_DARK = "qtcreator_dark" - QT_CREATOR_LIGHT = "qtcreator_light" - RAILSCASTS = "railscasts" - RAINBOW = "rainbow" - ROUTEROS = "routeros" - SCHOOL_BOOK = "school-book" - SHADES_OF_PURPLE = "shades-of-purple" - SOLARIZED_DARK = "solarized-dark" - SOLARIZED_LIGHT = "solarized-light" - SUNBURST = "sunburst" - TOMORROW_NIGHT_BLUE = "tomorrow-night-blue" - TOMORROW_NIGHT_BRIGHT = "tomorrow-night-bright" - TOMORROW_NIGHT_EIGHTIES = "tomorrow-night-eighties" - TOMORROW_NIGHT = "tomorrow-night" - TOMORROW = "tomorrow" - VS = "vs" - VS2015 = "vs2015" - XCODE = "xcode" - XT256 = "xt256" - ZENBURN = "zenburn" - - -@dataclass -class MarkdownCustomCodeTheme: - addition: Optional[TextStyle] = None - attr: Optional[TextStyle] = None - attribute: Optional[TextStyle] = None - built_in: Optional[TextStyle] = None - builtin_name: Optional[TextStyle] = None - bullet: Optional[TextStyle] = None - class_name: Optional[TextStyle] = None - code: Optional[TextStyle] = None - comment: Optional[TextStyle] = None - deletion: Optional[TextStyle] = None - doctag: Optional[TextStyle] = None - emphasis: Optional[TextStyle] = None - formula: Optional[TextStyle] = None - function: Optional[TextStyle] = None - keyword: Optional[TextStyle] = None - link: Optional[TextStyle] = None - link_label: Optional[TextStyle] = None - literal: Optional[TextStyle] = None - meta: Optional[TextStyle] = None - meta_keyword: Optional[TextStyle] = None - meta_string: Optional[TextStyle] = None - name: Optional[TextStyle] = None - number: Optional[TextStyle] = None - operator: Optional[TextStyle] = None - params: Optional[TextStyle] = None - pattern_match: Optional[TextStyle] = None - quote: Optional[TextStyle] = None - regexp: Optional[TextStyle] = None - root: Optional[TextStyle] = None - section: Optional[TextStyle] = None - selector_attr: Optional[TextStyle] = None - selector_class: Optional[TextStyle] = None - selector_id: Optional[TextStyle] = None - selector_pseudo: Optional[TextStyle] = None - selector_tag: Optional[TextStyle] = None - string: Optional[TextStyle] = None - strong: Optional[TextStyle] = None - stronge: Optional[TextStyle] = None - subst: Optional[TextStyle] = None - subtr: Optional[TextStyle] = None - symbol: Optional[TextStyle] = None - tag: Optional[TextStyle] = None - template_tag: Optional[TextStyle] = None - template_variable: Optional[TextStyle] = None - title: Optional[TextStyle] = None - type: Optional[TextStyle] = None - variable: Optional[TextStyle] = None - - -class Markdown(ConstrainedControl): - """ - Control for rendering text in markdown format. - - ----- - - Online docs: https://flet.dev/docs/controls/markdown - """ - - def __init__( - self, - value: Optional[str] = None, - selectable: Optional[bool] = None, - extension_set: Optional[MarkdownExtensionSet] = None, - code_theme: Optional[Union[MarkdownCodeTheme, MarkdownCustomCodeTheme]] = None, - auto_follow_links: Optional[bool] = None, - shrink_wrap: Optional[bool] = None, - fit_content: Optional[bool] = None, - soft_line_break: Optional[bool] = None, - auto_follow_links_target: Optional[str] = None, - img_error_content: Optional[Control] = None, - code_style_sheet: Optional[MarkdownStyleSheet] = None, - md_style_sheet: Optional[MarkdownStyleSheet] = None, - on_tap_text: OptionalControlEventCallable = None, - on_selection_change: OptionalEventCallable[TextSelectionChangeEvent] = None, - on_tap_link: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.__on_selection_change = EventHandler(lambda e: TextSelectionChangeEvent(e)) - - self._add_event_handler( - "selection_change", self.__on_selection_change.get_handler() - ) - - self.value = value - self.selectable = selectable - self.extension_set = extension_set - self.code_theme = code_theme - self.auto_follow_links = auto_follow_links - self.auto_follow_links_target = auto_follow_links_target - self.on_tap_link = on_tap_link - self.shrink_wrap = shrink_wrap - self.fit_content = fit_content - self.soft_line_break = soft_line_break - self.on_tap_text = on_tap_text - self.on_selection_change = on_selection_change - self.img_error_content = img_error_content - self.code_style_sheet = code_style_sheet - self.md_style_sheet = md_style_sheet - - def _get_control_name(self): - return "markdown" - - def before_update(self): - super().before_update() - self._set_attr_json("codeStyleSheet", self.__code_style_sheet) - self._set_attr_json("mdStyleSheet", self.__md_style_sheet) - self._set_attr_json( - "codeTheme", - self.__code_theme.value - if isinstance(self.__code_theme, MarkdownCodeTheme) - else self.__code_theme, - ) - - def _get_children(self): - if self.__img_error_content is not None: - self.__img_error_content._set_attr_internal("n", "error") - return [self.__img_error_content] - return [] - - # value - @property - def value(self) -> Optional[str]: - return self._get_attr("value") - - @value.setter - def value(self, value: Optional[str]): - self._set_attr("value", value) - - # selectable - @property - def selectable(self) -> bool: - return self._get_attr("selectable", data_type="bool", def_value=False) - - @selectable.setter - def selectable(self, value: Optional[bool]): - self._set_attr("selectable", value) - - # shrink_wrap - @property - def shrink_wrap(self) -> bool: - return self._get_attr("shrinkWrap", data_type="bool", def_value=True) - - @shrink_wrap.setter - def shrink_wrap(self, value: Optional[bool]): - self._set_attr("shrinkWrap", value) - - # fit_content - @property - def fit_content(self) -> bool: - return self._get_attr("fitContent", data_type="bool", def_value=True) - - @fit_content.setter - def fit_content(self, value: Optional[bool]): - self._set_attr("fitContent", value) - - # soft_line_break - @property - def soft_line_break(self) -> bool: - return self._get_attr("softLineBreak", data_type="bool", def_value=False) - - @soft_line_break.setter - def soft_line_break(self, value: Optional[bool]): - self._set_attr("softLineBreak", value) - - # extension_set - @property - def extension_set(self) -> Optional[MarkdownExtensionSet]: - return self.__extension_set - - @extension_set.setter - def extension_set(self, value: Optional[MarkdownExtensionSet]): - self.__extension_set = value - self._set_enum_attr("extensionSet", value, MarkdownExtensionSet) - - # code_style_sheet - @property - def code_style_sheet(self) -> Optional[MarkdownStyleSheet]: - return self.__code_style_sheet - - @code_style_sheet.setter - def code_style_sheet(self, value: Optional[MarkdownStyleSheet]): - self.__code_style_sheet = value - - # md_style_sheet - @property - def md_style_sheet(self) -> Optional[MarkdownStyleSheet]: - return self.__md_style_sheet - - @md_style_sheet.setter - def md_style_sheet(self, value: Optional[MarkdownStyleSheet]): - self.__md_style_sheet = value - - # code_theme - @property - def code_theme(self) -> Optional[Union[MarkdownCodeTheme, MarkdownCustomCodeTheme]]: - return self.__code_theme - - @code_theme.setter - def code_theme( - self, value: Optional[Union[MarkdownCodeTheme, MarkdownCustomCodeTheme]] - ): - self.__code_theme = value - - # auto_follow_links - @property - def auto_follow_links(self) -> bool: - return cast( - Optional[bool], - self._get_attr("autoFollowLinks", data_type="bool", def_value=False), - ) - - @auto_follow_links.setter - def auto_follow_links(self, value: Optional[bool]): - self._set_attr("autoFollowLinks", value) - - # auto_follow_links_target - @property - def auto_follow_links_target(self) -> Optional[str]: - return self._get_attr("autoFollowLinksTarget") - - @auto_follow_links_target.setter - def auto_follow_links_target(self, value: Optional[str]): - self._set_attr("autoFollowLinksTarget", value) - - # img_error_content - @property - def img_error_content(self) -> Optional[Control]: - return self.__img_error_content - - @img_error_content.setter - def img_error_content(self, value: Optional[Control]): - self.__img_error_content = value - - # on_tap_link - @property - def on_tap_link(self) -> OptionalControlEventCallable: - return self._get_event_handler("tap_link") - - @on_tap_link.setter - def on_tap_link(self, handler: OptionalControlEventCallable): - self._add_event_handler("tap_link", handler) - - # on_tap_text - @property - def on_tap_text(self) -> OptionalControlEventCallable: - return self._get_event_handler("tap_text") - - @on_tap_text.setter - def on_tap_text(self, handler: OptionalControlEventCallable): - self._add_event_handler("tap_text", handler) - - # on_selection_change - @property - def on_selection_change( - self, - ) -> OptionalEventCallable[TextSelectionChangeEvent]: - return self.__on_selection_change.handler - - @on_selection_change.setter - def on_selection_change( - self, - handler: OptionalEventCallable[TextSelectionChangeEvent], - ): - self.__on_selection_change.handler = handler diff --git a/sdk/python/packages/flet/src/flet/core/matplotlib_chart.py b/sdk/python/packages/flet/src/flet/core/matplotlib_chart.py deleted file mode 100644 index 9f3ce015f..000000000 --- a/sdk/python/packages/flet/src/flet/core/matplotlib_chart.py +++ /dev/null @@ -1,191 +0,0 @@ -import io -import re -import xml.etree.ElementTree as ET -from typing import Any, Optional, Union - -from flet.core import alignment -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.container import Container -from flet.core.control import OptionalNumber -from flet.core.image import Image -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ImageFit, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - -try: - from matplotlib.figure import Figure -except ImportError: - raise Exception( - 'Install "matplotlib" Python package to use MatplotlibChart control.' - ) - - -class MatplotlibChart(Container): - """ - Displays Matplotlib(https://matplotlib.org/) chart. - - Example: - ``` - import matplotlib - import matplotlib.pyplot as plt - - import flet as ft - from flet.core.matplotlib_chart import MatplotlibChart - - matplotlib.use("svg") - - - def main(page: ft.Page): - - fig, ax = plt.subplots() - - fruits = ["apple", "blueberry", "cherry", "orange"] - counts = [40, 100, 30, 55] - bar_labels = ["red", "blue", "_red", "orange"] - bar_colors = ["tab:red", "tab:blue", "tab:red", "tab:orange"] - - ax.bar(fruits, counts, label=bar_labels, color=bar_colors) - - ax.set_ylabel("fruit supply") - ax.set_title("Fruit supply by kind and color") - ax.legend(title="Fruit color") - - page.add(MatplotlibChart(fig, expand=True)) - - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/matplotlibchart - """ - - def __init__( - self, - figure: Optional[Figure] = None, - isolated: bool = False, - original_size: bool = False, - transparent: bool = False, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Container.__init__( - self, - ref=ref, - key=key, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.figure = figure - self.isolated = isolated - self.original_size = original_size - self.transparent = transparent - - def is_isolated(self): - return self.__isolated - - def build(self): - self.alignment = alignment.center - self.__img = Image(fit=ImageFit.FILL) - self.content = self.__img - - def before_update(self): - super().before_update() - if self.__figure is not None: - s = io.StringIO() - self.__figure.savefig(s, format="svg", transparent=self.__transparent) - svg = s.getvalue() - - if not self.__original_size: - root = ET.fromstring(svg) - w = float(re.findall(r"\d+", root.attrib["width"])[0]) - h = float(re.findall(r"\d+", root.attrib["height"])[0]) - self.__img.aspect_ratio = w / h - self.__img.src = svg - - # original_size - @property - def original_size(self): - return self.__original_size - - @original_size.setter - def original_size(self, value): - self.__original_size = value - - # isolated - @property - def isolated(self): - return self.__isolated - - @isolated.setter - def isolated(self, value): - self.__isolated = value - - # figure - @property - def figure(self): - return self.__figure - - @figure.setter - def figure(self, value): - self.__figure = value - - # transparent - @property - def transparent(self) -> bool: - return self.__transparent - - @transparent.setter - def transparent(self, value: bool): - self.__transparent = value diff --git a/sdk/python/packages/flet/src/flet/core/menu_bar.py b/sdk/python/packages/flet/src/flet/core/menu_bar.py deleted file mode 100644 index 9103c8c81..000000000 --- a/sdk/python/packages/flet/src/flet/core/menu_bar.py +++ /dev/null @@ -1,140 +0,0 @@ -from dataclasses import dataclass -from typing import Any, List, Optional, Sequence, Union - -from flet.core.alignment import Alignment -from flet.core.border import BorderSide -from flet.core.buttons import OutlinedBorder -from flet.core.control import Control -from flet.core.ref import Ref -from flet.core.types import ( - ClipBehavior, - ColorValue, - ControlState, - ControlStateValue, - MouseCursor, - OptionalNumber, - PaddingValue, - ResponsiveNumber, -) - - -@dataclass -class MenuStyle: - alignment: Optional[Alignment] = None - bgcolor: ControlStateValue[ColorValue] = None - shadow_color: ControlStateValue[ColorValue] = None - surface_tint_color: ControlStateValue[ColorValue] = None - elevation: ControlStateValue[OptionalNumber] = None - padding: ControlStateValue[PaddingValue] = None - side: ControlStateValue[BorderSide] = None - shape: ControlStateValue[OutlinedBorder] = None - mouse_cursor: ControlStateValue[MouseCursor] = None - - def __post_init__(self): - if not isinstance(self.padding, dict): - self.padding = {ControlState.DEFAULT: self.padding} - - if not isinstance(self.side, dict): - self.side = {ControlState.DEFAULT: self.side} - - if not isinstance(self.shape, dict): - self.shape = {ControlState.DEFAULT: self.shape} - - -class MenuBar(Control): - """ - A menu bar that manages cascading child menus. - - It could be placed anywhere but typically resides above the main body of the application - and defines a menu system for invoking callbacks in response to user selection of a menu item. - - ----- - - Online docs: https://flet.dev/docs/controls/menubar - """ - - def __init__( - self, - controls: Sequence[Control], - clip_behavior: Optional[ClipBehavior] = None, - style: Optional[MenuStyle] = None, - # - # Control - # - ref: Optional[Ref] = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - visible=visible, - disabled=disabled, - data=data, - ) - - self.controls = controls - self.clip_behavior = clip_behavior - self.style = style - - def _get_control_name(self): - return "menubar" - - def before_update(self): - super().before_update() - assert any( - c.visible for c in self.__controls - ), "MenuBar must have at minimum one visible control" - if self.__style is not None: - self.__style.side = self._wrap_attr_dict(self.__style.side) - self.__style.shape = self._wrap_attr_dict(self.__style.shape) - self.__style.mouse_cursor = self._wrap_attr_dict(self.__style.mouse_cursor) - if self.__style.mouse_cursor: - for k, v in self.__style.mouse_cursor.items(): - self.__style.mouse_cursor[k] = ( - v.value if isinstance(v, MouseCursor) else str(v) - ) - self._set_attr_json("style", self.__style) - - def _get_children(self): - return self.__controls - - def __contains__(self, item): - return item in self.__controls - - # controls - @property - def controls(self) -> List[Control]: - return self.__controls - - @controls.setter - def controls(self, value: Sequence[Control]): - self.__controls = list(value) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # style - @property - def style(self) -> Optional[MenuStyle]: - return self.__style - - @style.setter - def style(self, value: Optional[MenuStyle]): - self.__style = value diff --git a/sdk/python/packages/flet/src/flet/core/menu_item_button.py b/sdk/python/packages/flet/src/flet/core/menu_item_button.py deleted file mode 100644 index 0a2273a5e..000000000 --- a/sdk/python/packages/flet/src/flet/core/menu_item_button.py +++ /dev/null @@ -1,282 +0,0 @@ -import time -from typing import Any, Optional, Union - -from flet.core.alignment import Axis -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.buttons import ButtonStyle -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ClipBehavior, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class MenuItemButton(ConstrainedControl): - """ - A button for use in a MenuBar or on its own, that can be activated by click or keyboard navigation. - - ----- - - Online docs: https://flet.dev/docs/controls/menuitembutton - """ - - def __init__( - self, - content: Optional[Control] = None, - close_on_click: Optional[bool] = None, - focus_on_hover: Optional[bool] = None, - leading: Optional[Control] = None, - trailing: Optional[Control] = None, - clip_behavior: Optional[ClipBehavior] = None, - style: Optional[ButtonStyle] = None, - semantic_label: Optional[str] = None, - autofocus: Optional[bool] = None, - overflow_axis: Optional[Axis] = None, - on_click: OptionalControlEventCallable = None, - on_hover: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.content = content - self.leading = leading - self.trailing = trailing - self.clip_behavior = clip_behavior - self.style = style - self.close_on_click = close_on_click - self.focus_on_hover = focus_on_hover - self.on_click = on_click - self.on_hover = on_hover - self.on_focus = on_focus - self.on_blur = on_blur - self.semantic_label = semantic_label - self.autofocus = autofocus - self.overflow_axis = overflow_axis - - def _get_control_name(self): - return "menuitembutton" - - def before_update(self): - super().before_update() - if self.__style is not None: - self.__style.side = self._wrap_attr_dict(self.__style.side) - self.__style.shape = self._wrap_attr_dict(self.__style.shape) - self.__style.padding = self._wrap_attr_dict(self.__style.padding) - self._set_attr_json("style", self.__style) - - def _get_children(self): - children = [] - if self.__leading: - self.__leading._set_attr_internal("n", "leading") - children.append(self.__leading) - if self.__trailing: - self.__trailing._set_attr_internal("n", "trailing") - children.append(self.__trailing) - if self.__content: - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - return children - - def focus(self): - self._set_attr_json("focus", str(time.time())) - self.update() - - # focus_on_hover - @property - def focus_on_hover(self) -> bool: - return self._get_attr("focusOnHover", data_type="bool", def_value=True) - - @focus_on_hover.setter - def focus_on_hover(self, value: Optional[bool]): - self._set_attr("focusOnHover", value) - - # close_on_click - @property - def close_on_click(self) -> bool: - return self._get_attr("closeOnClick", data_type="bool", def_value=True) - - @close_on_click.setter - def close_on_click(self, value: Optional[bool]): - self._set_attr("closeOnClick", value) - - # semantic_label - @property - def semantic_label(self) -> Optional[str]: - return self._get_attr("semanticLabel") - - @semantic_label.setter - def semantic_label(self, value: Optional[str]): - self._set_attr("semanticLabel", value) - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # overflow_axis - @property - def overflow_axis(self) -> Optional[Axis]: - return self.__overflow_axis - - @overflow_axis.setter - def overflow_axis(self, value: Optional[Axis]): - self.__overflow_axis = value - self._set_enum_attr("overflowAxis", value, Axis) - - # leading - @property - def leading(self) -> Optional[Control]: - return self.__leading - - @leading.setter - def leading(self, value: Optional[Control]): - self.__leading = value - - # trailing - @property - def trailing(self) -> Optional[Control]: - return self.__trailing - - @trailing.setter - def trailing(self, value: Optional[Control]): - self.__trailing = value - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # style - @property - def style(self) -> Optional[ButtonStyle]: - return self.__style - - @style.setter - def style(self, value: Optional[ButtonStyle]): - self.__style = value - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # on_click - @property - def on_click(self): - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler): - self._add_event_handler("click", handler) - self._set_attr("onClick", True if handler is not None else None) - - # on_hover - @property - def on_hover(self) -> OptionalControlEventCallable: - return self._get_event_handler("hover") - - @on_hover.setter - def on_hover(self, handler: OptionalControlEventCallable): - self._add_event_handler("hover", handler) - self._set_attr("onHover", True if handler is not None else None) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) diff --git a/sdk/python/packages/flet/src/flet/core/merge_semantics.py b/sdk/python/packages/flet/src/flet/core/merge_semantics.py deleted file mode 100644 index 21539fade..000000000 --- a/sdk/python/packages/flet/src/flet/core/merge_semantics.py +++ /dev/null @@ -1,58 +0,0 @@ -from typing import Any, Optional - -from flet.core.control import Control -from flet.core.ref import Ref - - -class MergeSemantics(Control): - """ - A control that merges the semantics of its descendants. - - Causes all the semantics of the subtree rooted at this node to be merged into one node in the semantics tree. - - Used by accessibility tools, search engines, and other semantic analysis software to determine the meaning of the application. - - ----- - - Online docs: https://flet.dev/docs/controls/mergesemantics - """ - - def __init__( - self, - content: Optional[Control] = None, - # - # Control - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - visible=visible, - disabled=disabled, - data=data, - ) - - self.content = content - - def _get_control_name(self): - return "mergesemantics" - - def _get_children(self): - children = [] - if isinstance(self.__content, Control): - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - return children - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value diff --git a/sdk/python/packages/flet/src/flet/core/navigation_bar.py b/sdk/python/packages/flet/src/flet/core/navigation_bar.py deleted file mode 100644 index 46869c85b..000000000 --- a/sdk/python/packages/flet/src/flet/core/navigation_bar.py +++ /dev/null @@ -1,406 +0,0 @@ -from enum import Enum -from typing import Any, Callable, List, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.border import Border -from flet.core.buttons import OutlinedBorder -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control -from flet.core.ref import Ref -from flet.core.types import ( - ColorEnums, - ColorValue, - ControlStateValue, - IconEnums, - IconValueOrControl, - OffsetValue, - OptionalControlEventCallable, - OptionalNumber, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class NavigationBarLabelBehavior(Enum): - """Defines how the destinations' labels will be laid out and when they'll be displayed.""" - - ALWAYS_SHOW = "alwaysShow" - ALWAYS_HIDE = "alwaysHide" - ONLY_SHOW_SELECTED = "onlyShowSelected" - - -class NavigationBarDestination(AdaptiveControl, Control): - """Defines the appearance of the button items that are arrayed within the navigation bar. - - The value must be a list of two or more NavigationBarDestination instances.""" - - def __init__( - self, - label: Optional[str] = None, - icon: Optional[IconValueOrControl] = None, - selected_icon: Optional[IconValueOrControl] = None, - bgcolor: Optional[ColorValue] = None, - # - # Control - # - ref: Optional[Ref] = None, - tooltip: Optional[str] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - # - # AdaptiveControl - # - adaptive: Optional[bool] = None, - ): - Control.__init__( - self, - ref=ref, - tooltip=tooltip, - disabled=disabled, - visible=visible, - data=data, - ) - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.label = label - self.icon = icon - self.selected_icon = selected_icon - self.bgcolor = bgcolor - - def _get_control_name(self): - return "navigationbardestination" - - def _get_children(self): - children = [] - if isinstance(self.__icon, Control): - self.__icon._set_attr_internal("n", "icon") - children.append(self.__icon) - if isinstance(self.__selected_icon, Control): - self.__selected_icon._set_attr_internal("n", "selected_icon") - children.append(self.__selected_icon) - return children - - # icon - @property - def icon(self) -> Optional[IconValueOrControl]: - return self.__icon - - @icon.setter - def icon(self, value: Optional[IconValueOrControl]): - self.__icon = value - if not isinstance(value, Control): - self._set_enum_attr("icon", value, IconEnums) - - # selected_icon - @property - def selected_icon(self) -> Optional[IconValueOrControl]: - return self.__selected_icon - - @selected_icon.setter - def selected_icon(self, value: Optional[IconValueOrControl]): - self.__selected_icon = value - if not isinstance(value, Control): - self._set_enum_attr("selectedIcon", value, IconEnums) - - # label - @property - def label(self) -> Optional[str]: - return self._get_attr("label") - - @label.setter - def label(self, value: Optional[str]): - self._set_attr("label", value) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # tooltip - @property - def tooltip(self) -> Optional[str]: - return self._get_attr("tooltip") - - @tooltip.setter - def tooltip(self, value: Optional[str]): - self._set_attr("tooltip", value) - - -class NavigationBar(ConstrainedControl, AdaptiveControl): - """ - Material 3 Navigation Bar component. - - Navigation bars offer a persistent and convenient way to switch between primary destinations in an app. - - Example: - - ``` - import flet as ft - - def main(page: ft.Page): - page.title = "NavigationBar Example" - page.navigation_bar = ft.NavigationBar( - destinations=[ - ft.NavigationBarDestination(icon=ft.icons.EXPLORE, label="Explore"), - ft.NavigationBarDestination(icon=ft.icons.COMMUTE, label="Commute"), - ft.NavigationBarDestination( - icon=ft.icons.BOOKMARK_BORDER, - selected_icon=ft.icons.BOOKMARK, - label="Explore" - ), - ] - ) - page.add(ft.Text("Body!")) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/navigationbar - """ - - def __init__( - self, - destinations: Optional[List[NavigationBarDestination]] = None, - selected_index: Optional[int] = None, - bgcolor: Optional[ColorValue] = None, - label_behavior: Optional[NavigationBarLabelBehavior] = None, - elevation: OptionalNumber = None, - shadow_color: Optional[ColorValue] = None, - indicator_color: Optional[ColorValue] = None, - indicator_shape: Optional[OutlinedBorder] = None, - surface_tint_color: Optional[ColorValue] = None, - border: Optional[Border] = None, - animation_duration: Optional[int] = None, - overlay_color: ControlStateValue[ColorValue] = None, - label_padding: Optional[PaddingValue] = None, - on_change: OptionalControlEventCallable = None, - # - # ConstrainedControl and AdaptiveControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: Callable[..., None] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.destinations = destinations - self.selected_index = selected_index - self.label_behavior = label_behavior - self.bgcolor = bgcolor - self.elevation = elevation - self.shadow_color = shadow_color - self.indicator_color = indicator_color - self.indicator_shape = indicator_shape - self.surface_tint_color = surface_tint_color - self.border = border - self.on_change = on_change - self.animation_duration = animation_duration - self.overlay_color = overlay_color - self.label_padding = label_padding - - def _get_control_name(self): - return "navigationbar" - - def before_update(self): - super().before_update() - self._set_attr_json("indicatorShape", self.__indicator_shape) - self._set_attr_json("border", self.__border) - self._set_attr_json("overlayColor", self.__overlay_color, wrap_attr_dict=True) - self._set_attr_json("labelPadding", self.__label_padding) - - def _get_children(self): - return self.__destinations - - # destinations - @property - def destinations(self) -> Optional[List[NavigationBarDestination]]: - return self.__destinations - - @destinations.setter - def destinations(self, value: Optional[List[NavigationBarDestination]]): - self.__destinations = value if value else [] - - # label_padding - @property - def label_padding(self) -> Optional[PaddingValue]: - return self.__label_padding - - @label_padding.setter - def label_padding(self, value: Optional[PaddingValue]): - self.__label_padding = value - - # selected_index - @property - def selected_index(self) -> int: - return self._get_attr("selectedIndex", data_type="int", def_value=0) - - @selected_index.setter - def selected_index(self, value: Optional[int]): - self._set_attr("selectedIndex", value) - - # label_behavior - @property - def label_behavior(self) -> Optional[NavigationBarLabelBehavior]: - return self.__label_behavior - - @label_behavior.setter - def label_behavior(self, value: Optional[NavigationBarLabelBehavior]): - self.__label_behavior = value - self._set_enum_attr("labelBehavior", value, NavigationBarLabelBehavior) - - # overlay_color - @property - def overlay_color(self) -> ControlStateValue[str]: - return self.__overlay_color - - @overlay_color.setter - def overlay_color(self, value: ControlStateValue[str]): - self.__overlay_color = value - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # elevation - @property - def elevation(self) -> OptionalNumber: - return self._get_attr("elevation") - - @elevation.setter - def elevation(self, value: OptionalNumber): - self._set_attr("elevation", value) - - # shadow_color - @property - def shadow_color(self) -> Optional[ColorValue]: - return self.__shadow_color - - @shadow_color.setter - def shadow_color(self, value: Optional[ColorValue]): - self.__shadow_color = value - self._set_enum_attr("shadowColor", value, ColorEnums) - - # indicator_color - @property - def indicator_color(self) -> Optional[ColorValue]: - return self.__indicator_color - - @indicator_color.setter - def indicator_color(self, value: Optional[ColorValue]): - self.__indicator_color = value - self._set_enum_attr("indicatorColor", value, ColorEnums) - - # indicator_shape - @property - def indicator_shape(self) -> Optional[OutlinedBorder]: - return self.__indicator_shape - - @indicator_shape.setter - def indicator_shape(self, value: Optional[OutlinedBorder]): - self.__indicator_shape = value - - # surface_tint_color - @property - def surface_tint_color(self) -> Optional[ColorValue]: - return self.__surface_tint_color - - @surface_tint_color.setter - def surface_tint_color(self, value: Optional[ColorValue]): - self.__surface_tint_color = value - self._set_enum_attr("surfaceTintColor", value, ColorEnums) - - # border - @property - def border(self) -> Optional[Border]: - return self.__border - - @border.setter - def border(self, value: Optional[Border]): - self.__border = value - - # animation_duration - @property - def animation_duration(self) -> Optional[int]: - return self._get_attr("animationDuration", data_type="int") - - @animation_duration.setter - def animation_duration(self, value: Optional[int]): - self._set_attr("animationDuration", value) - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) diff --git a/sdk/python/packages/flet/src/flet/core/navigation_drawer.py b/sdk/python/packages/flet/src/flet/core/navigation_drawer.py deleted file mode 100644 index fb832ee1f..000000000 --- a/sdk/python/packages/flet/src/flet/core/navigation_drawer.py +++ /dev/null @@ -1,341 +0,0 @@ -from enum import Enum -from typing import Any, List, Optional, Sequence - -from flet.core.buttons import OutlinedBorder -from flet.core.control import Control -from flet.core.ref import Ref -from flet.core.types import ( - ColorEnums, - ColorValue, - IconEnums, - IconValueOrControl, - OptionalControlEventCallable, - OptionalNumber, - PaddingValue, -) - - -class NavigationDrawerDestination(Control): - """Displays an icon with a label, for use in NavigationDrawer destinations.""" - - def __init__( - self, - label: Optional[str] = None, - icon: Optional[IconValueOrControl] = None, - selected_icon: Optional[IconValueOrControl] = None, - bgcolor: Optional[ColorValue] = None, - # - # Control - # - ref: Optional[Ref] = None, - data: Any = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - ): - Control.__init__( - self, - ref=ref, - disabled=disabled, - data=data, - visible=visible, - ) - self.label = label - self.bgcolor = bgcolor - self.icon = icon - self.selected_icon = selected_icon - - def _get_control_name(self): - return "navigationdrawerdestination" - - def _get_children(self): - children = [] - if isinstance(self.__icon, Control): - self.__icon._set_attr_internal("n", "icon") - children.append(self.__icon) - if isinstance(self.__selected_icon, Control): - self.__selected_icon._set_attr_internal("n", "selected_icon") - children.append(self.__selected_icon) - return children - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgColor", value, ColorEnums) - - # icon - @property - def icon(self) -> Optional[IconValueOrControl]: - return self.__icon - - @icon.setter - def icon(self, value: Optional[IconValueOrControl]): - self.__icon = value - if not isinstance(value, Control): - self._set_enum_attr("icon", value, IconEnums) - - # selected_icon - @property - def selected_icon(self) -> Optional[IconValueOrControl]: - return self.__selected_icon - - @selected_icon.setter - def selected_icon(self, value: Optional[IconValueOrControl]): - self.__selected_icon = value - if not isinstance(value, Control): - self._set_enum_attr("selectedIcon", value, IconEnums) - - # label - @property - def label(self) -> Optional[str]: - return self._get_attr("label") - - @label.setter - def label(self, value: Optional[str]): - self._set_attr("label", value) - - -class NavigationDrawerPosition(Enum): - START = "start" - END = "end" - - -class NavigationDrawer(Control): - """ - Material Design Navigation Drawer component. - - Navigation Drawer is a panel slides in horizontally from the left or right edge of a page to show primary destinations in an app. - - Example: - - ``` - import flet as ft - - - def main(page: ft.Page): - page.horizontal_alignment = ft.CrossAxisAlignment.CENTER - - def handle_dismissal(e): - page.add(ft.Text("Drawer dismissed")) - - def handle_change(e): - page.add(ft.Text(f"Selected Index changed: {e.selected_index}")) - # page.close(drawer) - - drawer = ft.NavigationDrawer( - on_dismiss=handle_dismissal, - on_change=handle_change, - controls=[ - ft.Container(height=12), - ft.NavigationDrawerDestination( - label="Item 1", - icon=ft.icons.DOOR_BACK_DOOR_OUTLINED, - selected_icon_content=ft.Icon(ft.icons.DOOR_BACK_DOOR), - ), - ft.Divider(thickness=2), - ft.NavigationDrawerDestination( - icon_content=ft.Icon(ft.icons.MAIL_OUTLINED), - label="Item 2", - selected_icon=ft.icons.MAIL, - ), - ft.NavigationDrawerDestination( - icon_content=ft.Icon(ft.icons.PHONE_OUTLINED), - label="Item 3", - selected_icon=ft.icons.PHONE, - ), - ], - ) - - page.add(ft.ElevatedButton("Show drawer", on_click=lambda e: page.open(drawer))) - - - ft.app(main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/navigationdrawer - """ - - def __init__( - self, - controls: Optional[Sequence[Control]] = None, - open: bool = False, - selected_index: Optional[int] = None, - bgcolor: Optional[ColorValue] = None, - elevation: OptionalNumber = None, - indicator_color: Optional[ColorValue] = None, - indicator_shape: Optional[OutlinedBorder] = None, - shadow_color: Optional[ColorValue] = None, - surface_tint_color: Optional[ColorValue] = None, - tile_padding: Optional[PaddingValue] = None, - position: Optional[NavigationDrawerPosition] = None, - on_change: OptionalControlEventCallable = None, - on_dismiss: OptionalControlEventCallable = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - ): - Control.__init__( - self, - ref=ref, - visible=visible, - disabled=disabled, - data=data, - rtl=rtl, - ) - - self.open = open - self.controls = controls - self.selected_index = selected_index - self.bgcolor = bgcolor - self.elevation = elevation - self.indicator_color = indicator_color - self.indicator_shape = indicator_shape - self.shadow_color = shadow_color - self.surface_tint_color = surface_tint_color - self.tile_padding = tile_padding - self.position = position - self.on_change = on_change - self.on_dismiss = on_dismiss - - def _get_control_name(self): - return "navigationdrawer" - - def before_update(self): - super().before_update() - self._set_attr_json("indicatorShape", self.__indicator_shape) - self._set_attr_json("tilePadding", self.__tile_padding) - - def _get_children(self): - return self.__controls - - # open - @property - def open(self) -> bool: - return self._get_attr("open", data_type="bool", def_value=False) - - @open.setter - def open(self, value: Optional[bool]): - self._set_attr("open", value) - - # controls - @property - def controls(self) -> Optional[List[Control]]: - return self.__controls - - @controls.setter - def controls(self, value: Optional[Sequence[Control]]): - self.__controls = list(value) if value is not None else [] - - # selected_index - @property - def selected_index(self) -> int: - return self._get_attr("selectedIndex", data_type="int", def_value=0) - - @selected_index.setter - def selected_index(self, value: Optional[int]): - self._set_attr("selectedIndex", value) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # position - @property - def position(self) -> Optional[NavigationDrawerPosition]: - return self.__position - - @position.setter - def position(self, value: Optional[NavigationDrawerPosition]): - self.__position = value or NavigationDrawerPosition.START - - # elevation - @property - def elevation(self) -> OptionalNumber: - return self._get_attr("elevation") - - @elevation.setter - def elevation(self, value: OptionalNumber): - self._set_attr("elevation", value) - - # indicator_color - @property - def indicator_color(self) -> Optional[ColorValue]: - return self.__indicator_color - - @indicator_color.setter - def indicator_color(self, value: Optional[ColorValue]): - self.__indicator_color = value - self._set_enum_attr("indicatorColor", value, ColorEnums) - - # indicator_shape - @property - def indicator_shape(self) -> Optional[OutlinedBorder]: - return self.__indicator_shape - - @indicator_shape.setter - def indicator_shape(self, value: Optional[OutlinedBorder]): - self.__indicator_shape = value - - # shadow_color - @property - def shadow_color(self) -> Optional[ColorValue]: - return self.__shadow_color - - @shadow_color.setter - def shadow_color(self, value: Optional[ColorValue]): - self.__shadow_color = value - self._set_enum_attr("shadowColor", value, ColorEnums) - - # surface_tint_color - @property - def surface_tint_color(self) -> Optional[ColorValue]: - return self.__surface_tint_color - - @surface_tint_color.setter - def surface_tint_color(self, value: Optional[ColorValue]): - self.__surface_tint_color = value - self._set_enum_attr("surfaceTintColor", value, ColorEnums) - - # tile_padding - @property - def tile_padding(self) -> Optional[PaddingValue]: - return self.__tile_padding - - @tile_padding.setter - def tile_padding(self, value: Optional[PaddingValue]): - self.__tile_padding = value - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - - # on_dismiss - @property - def on_dismiss(self) -> OptionalControlEventCallable: - return self._get_event_handler("dismiss") - - @on_dismiss.setter - def on_dismiss(self, handler: OptionalControlEventCallable): - self._add_event_handler("dismiss", handler) diff --git a/sdk/python/packages/flet/src/flet/core/navigation_rail.py b/sdk/python/packages/flet/src/flet/core/navigation_rail.py deleted file mode 100644 index fe1ad4179..000000000 --- a/sdk/python/packages/flet/src/flet/core/navigation_rail.py +++ /dev/null @@ -1,472 +0,0 @@ -from enum import Enum -from typing import Any, Callable, List, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.buttons import OutlinedBorder -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.types import ( - ColorEnums, - ColorValue, - IconEnums, - IconValueOrControl, - OffsetValue, - OptionalControlEventCallable, - OptionalNumber, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class NavigationRailLabelType(Enum): - NONE = "none" - ALL = "all" - SELECTED = "selected" - - -class NavigationRailDestination(Control): - def __init__( - self, - icon: Optional[IconValueOrControl] = None, - selected_icon: Optional[IconValueOrControl] = None, - label: Optional[str] = None, - label_content: Optional[Control] = None, - padding: Optional[PaddingValue] = None, - indicator_color: Optional[ColorValue] = None, - indicator_shape: Optional[OutlinedBorder] = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - ) -> None: - Control.__init__(self, ref=ref, disabled=disabled, visible=visible, data=data) - self.label = label - self.icon = icon - self.selected_icon = selected_icon - self.label_content = label_content - self.padding = padding - self.indicator_color = indicator_color - self.indicator_shape = indicator_shape - - def _get_control_name(self): - return "navigationraildestination" - - def before_update(self) -> None: - super().before_update() - self._set_attr_json("padding", self.__padding) - if isinstance(self.__indicator_shape, OutlinedBorder): - self._set_attr_json("indicatorShape", self.__indicator_shape) - - def _get_children(self): - children = [] - if self.__label_content: - self.__label_content._set_attr_internal("n", "label_content") - children.append(self.__label_content) - if isinstance(self.__icon, Control): - self.__icon._set_attr_internal("n", "icon") - children.append(self.__icon) - if isinstance(self.__selected_icon, Control): - self.__selected_icon._set_attr_internal("n", "selected_icon") - children.append(self.__selected_icon) - return children - - # icon - @property - def icon(self) -> Optional[IconValueOrControl]: - return self.__icon - - @icon.setter - def icon(self, value: Optional[IconValueOrControl]): - self.__icon = value - if not isinstance(value, Control): - self._set_enum_attr("icon", value, IconEnums) - - # selected_icon - @property - def selected_icon(self) -> Optional[IconValueOrControl]: - return self.__selected_icon - - @selected_icon.setter - def selected_icon(self, value: Optional[IconValueOrControl]): - self.__selected_icon = value - if not isinstance(value, Control): - self._set_enum_attr("selectedIcon", value, IconEnums) - - # label - @property - def label(self) -> Optional[str]: - return self._get_attr("label") - - @label.setter - def label(self, value: Optional[str]): - self._set_attr("label", value) - - # label_content - @property - def label_content(self) -> Optional[Control]: - return self.__label_content - - @label_content.setter - def label_content(self, value: Optional[Control]): - self.__label_content = value - - # indicator_color - @property - def indicator_color(self) -> Optional[ColorValue]: - return self.__indicator_color - - @indicator_color.setter - def indicator_color(self, value: Optional[ColorValue]): - self.__indicator_color = value - self._set_enum_attr("indicatorColor", value, ColorEnums) - - # indicator_shape - @property - def indicator_shape(self) -> Optional[OutlinedBorder]: - return self.__indicator_shape - - @indicator_shape.setter - def indicator_shape(self, value: Optional[OutlinedBorder]): - self.__indicator_shape = value - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - -class NavigationRail(ConstrainedControl): - """ - A material widget that is meant to be displayed at the left or right of an app to navigate between a small number of views, typically between three and five. - - Example: - - ``` - import flet as ft - - def main(page: ft.Page): - - rail = ft.NavigationRail( - selected_index=1, - label_type=ft.NavigationRailLabelType.ALL, - # extended=True, - min_width=100, - min_extended_width=400, - leading=ft.FloatingActionButton(icon=ft.icons.CREATE, text="Add"), - group_alignment=-0.9, - destinations=[ - ft.NavigationRailDestination( - icon=ft.icons.FAVORITE_BORDER, selected_icon=ft.icons.FAVORITE, label="First" - ), - ft.NavigationRailDestination( - icon_content=ft.Icon(ft.icons.BOOKMARK_BORDER), - selected_icon_content=ft.Icon(ft.icons.BOOKMARK), - label="Second", - ), - ft.NavigationRailDestination( - icon=ft.icons.SETTINGS_OUTLINED, - selected_icon_content=ft.Icon(ft.icons.SETTINGS), - label_content=ft.Text("Settings"), - ), - ], - on_change=lambda e: print("Selected destination:", e.control.selected_index), - ) - - page.add( - ft.Row( - [ - rail, - ft.VerticalDivider(width=1), - ft.Column([ ft.Text("Body!")], alignment=ft.MainAxisAlignment.START, expand=True), - ], - expand=True, - ) - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/navigationrail - """ - - def __init__( - self, - destinations: Optional[List[NavigationRailDestination]] = None, - elevation: OptionalNumber = None, - selected_index: Optional[int] = None, - extended: Optional[bool] = None, - label_type: Optional[NavigationRailLabelType] = None, - bgcolor: Optional[ColorValue] = None, - indicator_color: Optional[ColorValue] = None, - indicator_shape: Optional[OutlinedBorder] = None, - leading: Optional[Control] = None, - trailing: Optional[Control] = None, - min_width: OptionalNumber = None, - min_extended_width: OptionalNumber = None, - group_alignment: OptionalNumber = None, - selected_label_text_style: Optional[TextStyle] = None, - unselected_label_text_style: Optional[TextStyle] = None, - on_change: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: Callable[..., None] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = False, - ): - ConstrainedControl.__init__( - self, - ref=ref, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - rtl=rtl, - ) - - self.destinations = destinations - self.selected_index = selected_index - self.elevation = elevation - self.extended = extended - self.label_type = label_type - self.bgcolor = bgcolor - self.indicator_color = indicator_color - self.indicator_shape = indicator_shape - self.leading = leading - self.trailing = trailing - self.min_width = min_width - self.min_extended_width = min_extended_width - self.group_alignment = group_alignment - self.on_change = on_change - self.selected_label_text_style = selected_label_text_style - self.unselected_label_text_style = unselected_label_text_style - - def _get_control_name(self): - return "navigationrail" - - def before_update(self): - super().before_update() - if isinstance(self.__indicator_shape, OutlinedBorder): - self._set_attr_json("indicatorShape", self.__indicator_shape) - if isinstance(self.__selected_label_text_style, TextStyle): - self._set_attr_json( - "selectedLabelTextStyle", self.__selected_label_text_style - ) - if isinstance(self.__unselected_label_text_style, TextStyle): - self._set_attr_json( - "unselectedLabelTextStyle", self.__unselected_label_text_style - ) - - def _get_children(self): - children = [] - if self.__leading: - self.__leading._set_attr_internal("n", "leading") - children.append(self.__leading) - if self.__trailing: - self.__trailing._set_attr_internal("n", "trailing") - children.append(self.__trailing) - return children + self.__destinations - - # destinations - @property - def destinations(self) -> Optional[List[NavigationRailDestination]]: - return self.__destinations - - @destinations.setter - def destinations(self, value: Optional[List[NavigationRailDestination]]): - self.__destinations = value if value else [] - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - - # selected_index - @property - def selected_index(self) -> int: - return self._get_attr("selectedIndex", data_type="int", def_value=0) - - @selected_index.setter - def selected_index(self, value: Optional[int]): - self._set_attr("selectedIndex", value) - - # label_type - @property - def label_type(self) -> Optional[NavigationRailLabelType]: - return self.__label_type - - @label_type.setter - def label_type(self, value: Optional[NavigationRailLabelType]): - self.__label_type = value - self._set_enum_attr("labelType", value, NavigationRailLabelType) - - # indicator_shape - @property - def indicator_shape(self) -> Optional[OutlinedBorder]: - return self.__indicator_shape - - @indicator_shape.setter - def indicator_shape(self, value: Optional[OutlinedBorder]): - self.__indicator_shape = value - - # indicator_color - @property - def indicator_color(self) -> Optional[ColorValue]: - return self.__indicator_color - - @indicator_color.setter - def indicator_color(self, value: Optional[ColorValue]): - self.__indicator_color = value - self._set_enum_attr("indicatorColor", value, ColorEnums) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # elevation - @property - def elevation(self) -> OptionalNumber: - return self._get_attr("elevation") - - @elevation.setter - def elevation(self, value: OptionalNumber): - assert value is None or value > 0, "elevation must be greater than 0" - self._set_attr("elevation", value) - - # extended - @property - def extended(self) -> bool: - return self._get_attr("extended", data_type="bool", def_value=False) - - @extended.setter - def extended(self, value: Optional[bool]): - self._set_attr("extended", value) - - # leading - @property - def leading(self) -> Optional[Control]: - return self.__leading - - @leading.setter - def leading(self, value: Optional[Control]): - self.__leading = value - - # trailing - @property - def trailing(self) -> Optional[Control]: - return self.__trailing - - @trailing.setter - def trailing(self, value: Optional[Control]): - self.__trailing = value - - # selected_label_text_style - @property - def selected_label_text_style(self) -> Optional[TextStyle]: - return self.__selected_label_text_style - - @selected_label_text_style.setter - def selected_label_text_style(self, value: Optional[TextStyle]): - self.__selected_label_text_style = value - - # unselected_label_text_style - @property - def unselected_label_text_style(self) -> Optional[TextStyle]: - return self.__unselected_label_text_style - - @unselected_label_text_style.setter - def unselected_label_text_style(self, value: Optional[TextStyle]): - self.__unselected_label_text_style = value - - # min_width - @property - def min_width(self) -> OptionalNumber: - return self._get_attr("minWidth", data_type="float") - - @min_width.setter - def min_width(self, value: OptionalNumber): - self._set_attr("minWidth", value) - - # min_extended_width - @property - def min_extended_width(self) -> OptionalNumber: - return self._get_attr("minExtendedWidth", data_type="float") - - @min_extended_width.setter - def min_extended_width(self, value: OptionalNumber): - self._set_attr("minExtendedWidth", value) - - # group_alignment - @property - def group_alignment(self) -> OptionalNumber: - return self._get_attr("groupAlignment", data_type="float") - - @group_alignment.setter - def group_alignment(self, value: OptionalNumber): - self._set_attr("groupAlignment", value) diff --git a/sdk/python/packages/flet/src/flet/core/outlined_button.py b/sdk/python/packages/flet/src/flet/core/outlined_button.py deleted file mode 100644 index 3c8ca5390..000000000 --- a/sdk/python/packages/flet/src/flet/core/outlined_button.py +++ /dev/null @@ -1,301 +0,0 @@ -import time -from typing import Any, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.buttons import ButtonStyle -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ClipBehavior, - ColorEnums, - ColorValue, - IconEnums, - IconValue, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, - UrlTarget, -) - - -class OutlinedButton(ConstrainedControl, AdaptiveControl): - """ - Outlined buttons are medium-emphasis buttons. They contain actions that are important, but aren’t the primary action in an app. Outlined buttons pair well with filled buttons to indicate an alternative, secondary action. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.title = "Basic outlined buttons" - page.add( - ft.OutlinedButton(text="Outlined button"), - ft.OutlinedButton("Disabled button", disabled=True), - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/outlinedbutton - """ - - def __init__( - self, - text: Optional[str] = None, - icon: Optional[IconValue] = None, - icon_color: Optional[ColorValue] = None, - content: Optional[Control] = None, - style: Optional[ButtonStyle] = None, - autofocus: Optional[bool] = None, - clip_behavior: Optional[ClipBehavior] = None, - url: Optional[str] = None, - url_target: Optional[UrlTarget] = None, - on_click: OptionalControlEventCallable = None, - on_long_press: OptionalControlEventCallable = None, - on_hover: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl and AdaptiveControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.text = text - self.icon = icon - self.icon_color = icon_color - self.style = style - self.content = content - self.autofocus = autofocus - self.url = url - self.url_target = url_target - self.clip_behavior = clip_behavior - self.on_click = on_click - self.on_long_press = on_long_press - self.on_hover = on_hover - self.on_focus = on_focus - self.on_blur = on_blur - - def _get_control_name(self): - return "outlinedbutton" - - def before_update(self): - super().before_update() - assert ( - self.text or self.icon or (self.__content and self.__content.visible) - ), "at minimum, text, icon or a visible content must be provided" - if self.__style is not None: - self.__style.side = self._wrap_attr_dict(self.__style.side) - self.__style.shape = self._wrap_attr_dict(self.__style.shape) - self.__style.padding = self._wrap_attr_dict(self.__style.padding) - self._set_attr_json("style", self.__style) - - def _get_children(self): - if self.__content is None: - return [] - self.__content._set_attr_internal("n", "content") - return [self.__content] - - def focus(self): - self._set_attr_json("focus", str(time.time())) - self.update() - - # text - @property - def text(self) -> Optional[str]: - return self._get_attr("text") - - @text.setter - def text(self, value: Optional[str]): - self._set_attr("text", value) - - # icon - @property - def icon(self) -> Optional[IconValue]: - return self.__icon - - @icon.setter - def icon(self, value: Optional[IconValue]): - self.__icon = value - self._set_enum_attr("icon", value, IconEnums) - - # icon_color - @property - def icon_color(self) -> Optional[ColorValue]: - return self.__icon_color - - @icon_color.setter - def icon_color(self, value: Optional[ColorValue]): - self.__icon_color = value - self._set_enum_attr("iconColor", value, ColorEnums) - - # style - @property - def style(self) -> Optional[ButtonStyle]: - return self.__style - - @style.setter - def style(self, value: Optional[ButtonStyle]): - self.__style = value - - # url - @property - def url(self) -> Optional[str]: - return self._get_attr("url") - - @url.setter - def url(self, value: Optional[str]): - self._set_attr("url", value) - - # url_target - @property - def url_target(self) -> Optional[UrlTarget]: - return self.__url_target - - @url_target.setter - def url_target(self, value: Optional[UrlTarget]): - self.__url_target = value - self._set_enum_attr("urlTarget", value, UrlTarget) - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) - - # on_long_press - @property - def on_long_press(self) -> OptionalControlEventCallable: - return self._get_event_handler("long_press") - - @on_long_press.setter - def on_long_press(self, handler: OptionalControlEventCallable): - self._add_event_handler("long_press", handler) - self._set_attr("onLongPress", True if handler is not None else None) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # on_hover - @property - def on_hover(self) -> OptionalControlEventCallable: - return self._get_event_handler("hover") - - @on_hover.setter - def on_hover(self, handler: OptionalControlEventCallable): - self._add_event_handler("hover", handler) - self._set_attr("onHover", True if handler is not None else None) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) diff --git a/sdk/python/packages/flet/src/flet/core/padding.py b/sdk/python/packages/flet/src/flet/core/padding.py deleted file mode 100644 index fc277000a..000000000 --- a/sdk/python/packages/flet/src/flet/core/padding.py +++ /dev/null @@ -1,24 +0,0 @@ -import dataclasses -from typing import Union - - -@dataclasses.dataclass -class Padding: - left: Union[float, int] - top: Union[float, int] - right: Union[float, int] - bottom: Union[float, int] - - -def all(value: float) -> Padding: - return Padding(left=value, top=value, right=value, bottom=value) - - -def symmetric(vertical: float = 0, horizontal: float = 0) -> Padding: - return Padding(left=horizontal, top=vertical, right=horizontal, bottom=vertical) - - -def only( - left: float = 0, top: float = 0, right: float = 0, bottom: float = 0 -) -> Padding: - return Padding(left=left, top=top, right=right, bottom=bottom) diff --git a/sdk/python/packages/flet/src/flet/core/page.py b/sdk/python/packages/flet/src/flet/core/page.py deleted file mode 100644 index 33c70c118..000000000 --- a/sdk/python/packages/flet/src/flet/core/page.py +++ /dev/null @@ -1,2028 +0,0 @@ -from __future__ import annotations - -import asyncio -import json -import logging -import threading -import time -import uuid -from concurrent.futures import CancelledError, Future, ThreadPoolExecutor -from contextvars import ContextVar -from dataclasses import dataclass -from datetime import datetime, timedelta, timezone -from functools import partial -from typing import ( - Any, - Awaitable, - Callable, - Coroutine, - Dict, - List, - Optional, - Sequence, - Tuple, - Type, - TypeVar, - Union, - cast, -) -from urllib.parse import urlparse - -import flet.core -from flet.core.adaptive_control import AdaptiveControl -from flet.core.alert_dialog import AlertDialog -from flet.core.alignment import Alignment -from flet.core.animation import AnimationCurve -from flet.core.app_bar import AppBar -from flet.core.banner import Banner -from flet.core.bottom_app_bar import BottomAppBar -from flet.core.bottom_sheet import BottomSheet -from flet.core.box import BoxDecoration -from flet.core.client_storage import ClientStorage -from flet.core.connection import Connection -from flet.core.control import Control -from flet.core.control_event import ControlEvent -from flet.core.cupertino_alert_dialog import CupertinoAlertDialog -from flet.core.cupertino_app_bar import CupertinoAppBar -from flet.core.cupertino_bottom_sheet import CupertinoBottomSheet -from flet.core.cupertino_navigation_bar import CupertinoNavigationBar -from flet.core.event import Event -from flet.core.event_handler import EventHandler -from flet.core.floating_action_button import FloatingActionButton -from flet.core.locks import NopeLock -from flet.core.navigation_bar import NavigationBar -from flet.core.navigation_drawer import NavigationDrawer, NavigationDrawerPosition -from flet.core.padding import Padding -from flet.core.protocol import Command -from flet.core.pubsub.pubsub_client import PubSubClient -from flet.core.querystring import QueryString -from flet.core.scrollable_control import OnScrollEvent -from flet.core.session_storage import SessionStorage -from flet.core.snack_bar import SnackBar -from flet.core.theme import Theme -from flet.core.types import ( - AppLifecycleState, - Brightness, - ColorEnums, - ColorValue, - CrossAxisAlignment, - FloatingActionButtonLocation, - LocaleConfiguration, - MainAxisAlignment, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - OptionalNumber, - PaddingValue, - PagePlatform, - ScrollMode, - ThemeMode, - WindowEventType, - Wrapper, -) -from flet.core.view import View -from flet.utils import classproperty, is_pyodide - -try: - from typing import ParamSpec -except ImportError: - from typing_extensions import ParamSpec - - -logger = logging.getLogger(flet.__name__) -try: - from typing import ParamSpec -except ImportError: - from typing_extensions import ParamSpec - -_session_page = ContextVar("flet_session_page", default=None) - - -class context: - @classproperty - def page(cls) -> "Page": - return _session_page.get() - - -try: - from flet.auth.authorization import Authorization - from flet.auth.oauth_provider import OAuthProvider -except ImportError: - - class OAuthProvider: ... - - class Authorization: - def __init__( - self, - provider: OAuthProvider, - fetch_user: bool, - fetch_groups: bool, - scope: Optional[List[str]] = None, - ): ... - - -AT = TypeVar("AT", bound=Authorization) - -InputT = ParamSpec("InputT") -RetT = TypeVar("RetT") - - -class PageDisconnectedException(Exception): - def __init__(self, message): - super().__init__(message) - - -class BrowserContextMenu: - def __init__(self, page: "Page"): - self.page = page - self.__disabled = False - - def enable(self, wait_timeout: Optional[float] = 10): - self.page._invoke_method("enableBrowserContextMenu", wait_timeout=wait_timeout) - self.__disabled = False - - def disable(self, wait_timeout: Optional[float] = 10): - self.page._invoke_method("disableBrowserContextMenu", wait_timeout=wait_timeout) - self.__disabled = True - - @property - def disabled(self) -> bool: - return self.__disabled - - -class Window: - def __init__(self, page: "Page"): - self.page = page - self.__alignment = None - self.__bgcolor = None - self.__on_event = EventHandler(lambda e: WindowEvent(e)) - self.page._add_event_handler( - "window_event", - self.__on_event.get_handler(), - ) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self.page._set_enum_attr("windowBgcolor", value, ColorEnums) - - # width - @property - def width(self) -> OptionalNumber: - w = self.page._get_attr("windowWidth") - return float(w) if w else 0 - - @width.setter - def width(self, value: OptionalNumber): - self.page._set_attr("windowWidth", value) - - # height - @property - def height(self) -> OptionalNumber: - h = self.page._get_attr("windowHeight") - return float(h) if h else 0 - - @height.setter - def height(self, value: OptionalNumber): - self.page._set_attr("windowHeight", value) - - # top - @property - def top(self) -> OptionalNumber: - w = self.page._get_attr("windowTop") - return float(w) if w else 0 - - @top.setter - def top(self, value: OptionalNumber): - self.page._set_attr("windowTop", value) - - # left - @property - def left(self) -> OptionalNumber: - h = self.page._get_attr("windowLeft") - return float(h) if h else 0 - - @left.setter - def left(self, value: OptionalNumber): - self.page._set_attr("windowLeft", value) - - # max_width - @property - def max_width(self) -> OptionalNumber: - return self.page._get_attr("windowMaxWidth") - - @max_width.setter - def max_width(self, value: OptionalNumber): - self.page._set_attr("windowMaxWidth", value) - - # max_height - @property - def max_height(self) -> OptionalNumber: - return self.page._get_attr("windowMaxHeight") - - @max_height.setter - def max_height(self, value: OptionalNumber): - self.page._set_attr("windowMaxHeight", value) - - # min_width - @property - def min_width(self) -> OptionalNumber: - return self.page._get_attr("windowMinWidth") - - @min_width.setter - def min_width(self, value: OptionalNumber): - self.page._set_attr("windowMinWidth", value) - - # min_height - @property - def min_height(self) -> OptionalNumber: - return self.page._get_attr("windowMinHeight") - - @min_height.setter - def min_height(self, value: OptionalNumber): - self.page._set_attr("windowMinHeight", value) - - # opacity - @property - def opacity(self) -> float: - return self.page._get_attr("windowOpacity", data_type="float", def_value=1.0) - - @opacity.setter - def opacity(self, value: OptionalNumber): - self.page._set_attr("windowOpacity", value) - - # maximized - @property - def maximized(self) -> bool: - return self.page._get_attr("windowMaximized", data_type="bool", def_value=False) - - @maximized.setter - def maximized(self, value: Optional[bool]): - self.page._set_attr("windowMaximized", value) - - # minimized - @property - def minimized(self) -> bool: - return self.page._get_attr("windowMinimized", data_type="bool", def_value=False) - - @minimized.setter - def minimized(self, value: Optional[bool]): - self.page._set_attr("windowMinimized", value) - - # minimizable - @property - def minimizable(self) -> bool: - return self.page._get_attr( - "windowMinimizable", data_type="bool", def_value=True - ) - - @minimizable.setter - def minimizable(self, value: Optional[bool]): - self.page._set_attr("windowMinimizable", value) - - # maximizable - @property - def maximizable(self) -> bool: - return self.page._get_attr( - "windowMaximizable", data_type="bool", def_value=True - ) - - @maximizable.setter - def maximizable(self, value: Optional[bool]): - self.page._set_attr("windowMaximizable", value) - - # resizable - @property - def resizable(self) -> bool: - return self.page._get_attr("windowResizable", data_type="bool", def_value=True) - - @resizable.setter - def resizable(self, value: Optional[bool]): - self.page._set_attr("windowResizable", value) - - # movable - @property - def movable(self) -> bool: - return self.page._get_attr("windowMovable", data_type="bool", def_value=True) - - @movable.setter - def movable(self, value: Optional[bool]): - self.page._set_attr("windowMovable", value) - - # full_screen - @property - def full_screen(self) -> bool: - return self.page._get_attr( - "windowFullScreen", data_type="bool", def_value=False - ) - - @full_screen.setter - def full_screen(self, value: Optional[bool]): - self.page._set_attr("windowFullScreen", value) - - # always_on_top - @property - def always_on_top(self) -> bool: - return self.page._get_attr( - "windowAlwaysOnTop", data_type="bool", def_value=False - ) - - @always_on_top.setter - def always_on_top(self, value: Optional[bool]): - self.page._set_attr("windowAlwaysOnTop", value) - - # prevent_close - @property - def prevent_close(self) -> bool: - return self.page._get_attr( - "windowPreventClose", data_type="bool", def_value=False - ) - - @prevent_close.setter - def prevent_close(self, value: Optional[bool]): - self.page._set_attr("windowPreventClose", value) - - # title_bar_hidden - @property - def title_bar_hidden(self) -> bool: - return self.page._get_attr( - "windowTitleBarHidden", data_type="bool", def_value=False - ) - - @title_bar_hidden.setter - def title_bar_hidden(self, value: Optional[bool]): - self.page._set_attr("windowTitleBarHidden", value) - - # title_bar_buttons_hidden - @property - def title_bar_buttons_hidden(self) -> bool: - return self.page._get_attr( - "windowTitleBarButtonsHidden", data_type="bool", def_value=False - ) - - @title_bar_buttons_hidden.setter - def title_bar_buttons_hidden(self, value: Optional[bool]): - self.page._set_attr("windowTitleBarButtonsHidden", value) - - # skip_task_bar - @property - def skip_task_bar(self) -> bool: - return self.page._get_attr( - "windowSkipTaskBar", data_type="bool", def_value=False - ) - - @skip_task_bar.setter - def skip_task_bar(self, value: Optional[bool]): - self.page._set_attr("windowSkipTaskBar", value) - - # frameless - @property - def frameless(self) -> bool: - return self.page._get_attr("windowFrameless", data_type="bool", def_value=False) - - @frameless.setter - def frameless(self, value: Optional[bool]): - self.page._set_attr("windowFrameless", value) - - # progress_bar - @property - def progress_bar(self) -> OptionalNumber: - return self.page._get_attr("windowProgressBar") - - @progress_bar.setter - def progress_bar(self, value: OptionalNumber): - self.page._set_attr("windowProgressBar", value) - - # focused - @property - def focused(self) -> bool: - return self.page._get_attr("windowFocused", data_type="bool", def_value=True) - - @focused.setter - def focused(self, value: Optional[bool]): - self.page._set_attr("windowFocused", value) - - # visible - @property - def visible(self) -> bool: - return self.page._get_attr("windowVisible", data_type="bool", def_value=True) - - @visible.setter - def visible(self, value: Optional[bool]): - self.page._set_attr("windowVisible", value) - - # always_on_bottom - @property - def always_on_bottom(self) -> bool: - return self.page._get_attr( - "windowAlwaysOnBottom", data_type="bool", def_value=False - ) - - @always_on_bottom.setter - def always_on_bottom(self, value: Optional[bool]): - self.page._set_attr("windowAlwaysOnBottom", value) - - # wait_until_ready_to_show - @property - def wait_until_ready_to_show(self) -> bool: - return self.page._get_attr( - "windowWaitUntilReadyToShow", data_type="bool", def_value=False - ) - - @wait_until_ready_to_show.setter - def wait_until_ready_to_show(self, value: Optional[bool]): - self.page._set_attr("windowWaitUntilReadyToShow", value) - - # shadow - @property - def shadow(self) -> bool: - return self.page._get_attr("windowShadow", data_type="bool", def_value=False) - - @shadow.setter - def shadow(self, value: Optional[bool]): - self.page._set_attr("windowShadow", value) - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value - - # badge_label - @property - def badge_label(self) -> Optional[str]: - return self.page._get_attr("windowBadgeLabel") - - @badge_label.setter - def badge_label(self, value: Optional[str]): - self.page._set_attr("windowBadgeLabel", value) - - # icon - @property - def icon(self) -> Optional[str]: - return self.page._get_attr("windowIcon") - - @icon.setter - def icon(self, value: Optional[str]): - self.page._set_attr("windowIcon", value) - - # ignore_mouse_events - @property - def ignore_mouse_events(self) -> bool: - return self.page._get_attr( - "windowIgnoreMouseEvents", data_type="bool", def_value=False - ) - - @ignore_mouse_events.setter - def ignore_mouse_events(self, value: Optional[bool]): - self.page._set_attr("windowIgnoreMouseEvents", value) - - # Methods - def destroy(self): - self.page._set_attr("windowDestroy", True) - self.page.update() - - def center(self) -> None: - self.page._set_attr("windowCenter", str(time.time())) - self.page.update() - - def close(self) -> None: - self.page._set_attr("windowClose", str(time.time())) - self.page.update() - - def to_front(self) -> None: - self.page._invoke_method("windowToFront") - - def start_dragging(self) -> None: - self.page._invoke_method("windowStartDragging") - - # Events - # on_event - @property - def on_event(self) -> OptionalEventCallable["WindowEvent"]: - return self.__on_event.handler - - @on_event.setter - def on_event( - self, - handler: OptionalEventCallable["WindowEvent"], - ): - self.__on_event.handler = handler - - -class Page(AdaptiveControl): - """ - Page is a container for `View` (https://flet.dev/docs/controls/view) controls. - - A page instance and the root view are automatically created when a new user session started. - - Example: - - ``` - import flet as ft - - - def main(page: ft.Page): - page.title = "New page" - page.add(ft.Text("Hello")) - - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/page - """ - - def __init__( - self, - conn: Connection, - session_id, - loop: asyncio.AbstractEventLoop, - executor: Optional[ThreadPoolExecutor] = None, - ) -> None: - Control.__init__(self) - self._id = "page" - self._Control__uid = "page" - self.__window = Window(self) - self.__browser_context_menu = BrowserContextMenu(self) - self.__conn = conn - self.__next_control_id = 1 - self.__snapshot: Dict[str, Dict[str, Any]] = {} - self.__expires_at = None - self.__query: QueryString = QueryString(page=self) # Querystring - self._session_id = session_id - self.__loop = loop - self.__executor = executor - self._index = {self._Control__uid: self} # index with all page controls - - self.__lock = threading.Lock() if not is_pyodide() else NopeLock() - - self.__views = [View()] - self.__default_view = self.__views[0] - self._controls = self.__default_view.controls - - self.__fonts: Optional[Dict[str, str]] = None - self.__offstage = Offstage() - self.__theme = None - self.__dark_theme = None - self.__locale_configuration = None - self.__theme_mode = ThemeMode.SYSTEM # Default Theme Mode - self.__pubsub: PubSubClient = PubSubClient(conn.pubsubhub, session_id) - self.__client_storage: ClientStorage = ClientStorage(self) - self.__session_storage: SessionStorage = SessionStorage(self) - self.__authorization: Optional[Authorization] = None - - self.__on_close = EventHandler() - self._add_event_handler("close", self.__on_close.get_handler()) - self.__on_platform_brightness_change = EventHandler() - self._add_event_handler( - "platformBrightnessChange", - self.__on_platform_brightness_change.get_handler(), - ) - - self.__on_app_lifecycle_state_change = EventHandler( - lambda e: AppLifecycleStateChangeEvent(e) - ) - self._add_event_handler( - "app_lifecycle_state_change", - self.__on_app_lifecycle_state_change.get_handler(), - ) - self.__on_resized = EventHandler(lambda e: WindowResizeEvent(e)) - self._add_event_handler("resized", self.__on_resized.get_handler()) - - self.__last_route = None - - # authorize/login/logout - - self.__on_login = EventHandler() - self._add_event_handler("authorize", self.__on_authorize_async) - self.__on_logout = EventHandler() - - # route_change - - def convert_route_change_event(e): - if self.__last_route == e.data: - return None # avoid duplicate calls - self.__last_route = e.data - self._set_attr("route", e.data, False) - self.query() # Update query url (required when manually changed from browser) - return RouteChangeEvent(route=e.data) - - self.__on_route_change: EventHandler = EventHandler(convert_route_change_event) - self._add_event_handler("route_change", self.__on_route_change.get_handler()) - - def convert_view_pop_event(e): - # e.data contains route name - view = next((v for v in self.views if v.route == e.data), None) - return ViewPopEvent(view=view) if view in self.views else None - - self.__on_view_pop = EventHandler(convert_view_pop_event) - self._add_event_handler("view_pop", self.__on_view_pop.get_handler()) - - def convert_keyboard_event(e): - d = json.loads(e.data) - return KeyboardEvent(**d) - - self.__on_keyboard_event = EventHandler(convert_keyboard_event) - self._add_event_handler( - "keyboard_event", self.__on_keyboard_event.get_handler() - ) - - def convert_page_media_change_event(e): - d = json.loads(e.data) - return PageMediaData(**d) - - self.__on_page_media_change_event = EventHandler( - convert_page_media_change_event - ) - self._add_event_handler( - "mediaChange", self.__on_page_media_change_event.get_handler() - ) - - self.__method_calls: Dict[str, Union[threading.Event, asyncio.Event]] = {} - self.__method_call_results: Dict[ - Union[threading.Event, asyncio.Event], tuple[Optional[str], Optional[str]] - ] = {} - self._add_event_handler("invoke_method_result", self.__on_invoke_method_result) - - self.__on_connect = EventHandler() - self._add_event_handler("connect", self.__on_connect.get_handler()) - self.__on_disconnect = EventHandler() - self._add_event_handler("disconnect", self.__on_disconnect.get_handler()) - self.__on_error = EventHandler() - self._add_event_handler("error", self.__on_error.get_handler()) - - _session_page.set(self) - - def get_control(self, id: str) -> Control: - return self._index.get(id) - - def before_update(self) -> None: - super().before_update() - self._set_attr_json("fonts", self.__fonts) - self._set_attr_json("theme", self.__theme) - self._set_attr_json("localeConfiguration", self.__locale_configuration) - self._set_attr_json("darkTheme", self.__dark_theme) - self._set_attr_json("windowAlignment", self.__window.alignment) - - def _get_control_name(self): - return "page" - - def _get_children(self): - children = [] - children.extend(self.__views) - children.append(self.__offstage) - return children - - def get_next_control_id(self) -> int: - r = self.__next_control_id - self.__next_control_id += 1 - return r - - async def fetch_page_details_async(self) -> None: - assert self.__conn - props = [ - "route", - "pwa", - "web", - "debug", - "platform", - "platformBrightness", - "media", - "width", - "height", - "windowWidth", - "windowHeight", - "windowTop", - "windowLeft", - "clientIP", - "clientUserAgent", - ] - values = ( - self.__conn.send_commands( - self._session_id, - [Command(0, "get", ["page", prop]) for prop in props], - ) - ).results - for i in range(len(props)): - self._set_attr(props[i], values[i], False) - - async def _connect(self, conn: Connection) -> None: - _session_page.set(self) - self.__conn = conn - self.__expires_at = None - await self.on_event_async(Event("page", "connect", "")) - - async def _disconnect(self, session_timeout_seconds: int) -> None: - self.__expires_at = datetime.now(timezone.utc) + timedelta( - seconds=session_timeout_seconds - ) - await self.on_event_async(Event("page", "disconnect", "")) - - def update(self, *controls) -> None: - with self.__lock: - if len(controls) == 0: - r = self.__update(self) - else: - r = self.__update(*controls) - self.__handle_mount_unmount(*r) - - def add(self, *controls: Control) -> None: - with self.__lock: - self._controls.extend(controls) - r = self.__update(self) - self.__handle_mount_unmount(*r) - - def insert(self, at: int, *controls: Control) -> None: - with self.__lock: - n = at - for control in controls: - self._controls.insert(n, control) - n += 1 - r = self.__update(self) - self.__handle_mount_unmount(*r) - - def remove(self, *controls: Control) -> None: - with self.__lock: - for control in controls: - self._controls.remove(control) - r = self.__update(self) - self.__handle_mount_unmount(*r) - - def remove_at(self, index: int) -> None: - with self.__lock: - self._controls.pop(index) - r = self.__update(self) - self.__handle_mount_unmount(*r) - - def clean(self) -> None: - self._clean(self) - self._controls.clear() - - def _clean(self, control: Control) -> None: - with self.__lock: - control._previous_children.clear() - assert control.uid is not None - removed_controls = [] - for child in control._get_children(): - removed_controls.extend( - self._remove_control_recursively(self.index, child) - ) - self._send_command("clean", [control.uid]) - for c in removed_controls: - c.will_unmount() - - def _close(self) -> None: - self.__pubsub.unsubscribe_all() - removed_controls = self._remove_control_recursively(self.index, self) - for c in removed_controls: - c.will_unmount() - c._dispose() - self._controls.clear() - self._previous_children.clear() - self.__on_view_pop = None - self.__client_storage = None - self.__session_storage = None - self.__conn = None - - def __update(self, *controls: Control) -> Tuple[List[Control], List[Control]]: - if not self.__conn: - raise PageDisconnectedException("Page has been disconnected") - commands, added_controls, removed_controls = self.__prepare_update(*controls) - self.__validate_controls_page(added_controls) - results = self.__conn.send_commands(self._session_id, commands).results - self.__update_control_ids(added_controls, results) - return added_controls, removed_controls - - def __prepare_update( - self, *controls: Control - ) -> Tuple[List[Any], List[Control], List[Control]]: - added_controls = [] - removed_controls = [] - commands = [] - - # build commands - - for control in controls: - control.build_update_commands( - self._index, commands, added_controls, removed_controls - ) - return commands, added_controls, removed_controls - - def __validate_controls_page(self, added_controls: List[Control]) -> None: - for ctrl in added_controls: - if ctrl.page and ctrl.page != self: - raise Exception( - f"Control has already been added to another page: {ctrl}" - ) - - def __update_control_ids( - self, added_controls: List[Control], results: List[Any] - ) -> None: - if len(results) > 0: - n = 0 - for line in results: - for id in line.split(" "): - added_controls[n]._Control__uid = id - - # add to index - self._index[id] = added_controls[n] - - n += 1 - - def __handle_mount_unmount(self, added_controls, removed_controls) -> None: - for ctrl in removed_controls: - ctrl.will_unmount() - ctrl.parent = None # remove parent reference - ctrl.page = None - for ctrl in added_controls: - ctrl.did_mount() - - def error(self, message: str = "") -> None: - with self.__lock: - self._send_command("error", [message]) - - async def on_event_async(self, e: Event) -> None: - logger.debug(f"page.on_event_async: {e.target} {e.name} {e.data}") - - if e.target == "page" and e.name == "change": - with self.__lock: - self.__on_page_change_event(e.data) - elif e.target in self._index: - ce = ControlEvent(e.target, e.name, e.data, self._index[e.target], self) - handler = self._index[e.target].event_handlers.get(e.name) - if handler: - if asyncio.iscoroutinefunction(handler): - await handler(ce) - else: - self.run_thread(handler, ce) - - def __on_page_change_event(self, data: str) -> None: - for props in json.loads(data): - id = props["i"] - if id in self._index: - for name in props: - if name != "i": - self._index[id]._set_attr(name, props[name], dirty=False) - if id in self.__snapshot: - self.__snapshot[id][name] = props[name] - - def run_task( - self, - handler: Callable[InputT, Awaitable[RetT]], - *args: InputT.args, - **kwargs: InputT.kwargs, - ) -> Future[RetT]: - _session_page.set(self) - assert asyncio.iscoroutinefunction(handler) - - future = asyncio.run_coroutine_threadsafe(handler(*args, **kwargs), self.__loop) - - def _on_completion(f): - try: - exception = f.exception() - if exception: - raise exception - except CancelledError: - pass - - future.add_done_callback(_on_completion) - - return future - - def __context_wrapper(self, handler: Callable[..., Any]) -> Wrapper: - def wrapper(*args,**kwargs): - _session_page.set(self) - handler(*args,**kwargs) - - return wrapper - - def run_thread( - self, - handler: Callable[InputT, Any], - *args: InputT.args, - **kwargs: InputT.kwargs, - ) -> None: - handler_with_context = self.__context_wrapper(handler) - if is_pyodide(): - handler_with_context(*args, **kwargs) - else: - assert self.__loop - self.__loop.call_soon_threadsafe( - self.__loop.run_in_executor, - self.__executor, - partial(handler_with_context, *args, **kwargs), - ) - - def go( - self, route: str, skip_route_change_event: bool = False, **kwargs: Any - ) -> None: - self.route = route if not kwargs else route + self.query.post(kwargs) - - if not skip_route_change_event: - self.run_task( - self.__on_route_change.get_handler(), - ControlEvent( - target="page", - name="route_change", - data=self.route, - page=self, - control=self, - ), - ) - self.update() - self.query() # Update query url (required when using go) - - def get_upload_url(self, file_name: str, expires: int) -> str: - r = self._send_command( - "getUploadUrl", attrs={"file": file_name, "expires": str(expires)} - ) - if r.error: - raise Exception(r.error) - return r.result - - def login( - self, - provider: OAuthProvider, - fetch_user: Optional[bool] = True, - fetch_groups: Optional[bool] = False, - scope: Optional[List[str]] = None, - saved_token: Optional[str] = None, - on_open_authorization_url: Optional[Callable[[str], None]] = None, - complete_page_html: Optional[str] = None, - redirect_to_page: Optional[bool] = False, - authorization: Type[AT] = Authorization, - ) -> AT: - self.__authorization = authorization( - provider, - fetch_user=fetch_user, - fetch_groups=fetch_groups, - scope=scope, - ) - if saved_token is None: - authorization_url, state = self.__authorization.get_authorization_data() - auth_attrs = {"state": state} - if complete_page_html: - auth_attrs["completePageHtml"] = complete_page_html - if redirect_to_page: - up = urlparse(provider.redirect_url) - auth_attrs["completePageUrl"] = up._replace( - path=self.__conn.page_name - ).geturl() - result = self._send_command("oauthAuthorize", attrs=auth_attrs) - if result.error != "": - raise Exception(result.error) - if on_open_authorization_url: - on_open_authorization_url(authorization_url) - else: - self.launch_url( - authorization_url, "flet_oauth_signin", web_popup_window=self.web - ) - else: - self.__authorization.dehydrate_token(saved_token) - self.run_task( - self.__on_login.get_handler(), - LoginEvent( - error="", - error_description="", - page=self, - control=self, - target="page", - name="on_login", - data="", - ), - ) - return self.__authorization - - async def login_async( - self, - provider: OAuthProvider, - fetch_user: Optional[bool] = True, - fetch_groups: Optional[bool] = False, - scope: Optional[List[str]] = None, - saved_token: Optional[str] = None, - on_open_authorization_url: Optional[ - Callable[[str], Coroutine[Any, Any, None]] - ] = None, - complete_page_html: Optional[str] = None, - redirect_to_page: Optional[bool] = False, - authorization: Type[AT] = Authorization, - ) -> AT: - self.__authorization = authorization( - provider, - fetch_user=fetch_user, - fetch_groups=fetch_groups, - scope=scope, - ) - if saved_token is None: - authorization_url, state = self.__authorization.get_authorization_data() - auth_attrs = {"state": state} - if complete_page_html: - auth_attrs["completePageHtml"] = complete_page_html - if redirect_to_page: - up = urlparse(provider.redirect_url) - auth_attrs["completePageUrl"] = up._replace( - path=f"{self.__conn.page_name}{self.route}" - ).geturl() - result = self._send_command("oauthAuthorize", attrs=auth_attrs) - if result.error != "": - raise Exception(result.error) - if on_open_authorization_url: - await on_open_authorization_url(authorization_url) - else: - self.launch_url( - authorization_url, "flet_oauth_signin", web_popup_window=self.web - ) - else: - await self.__authorization.dehydrate_token_async(saved_token) - self.run_task( - self.__on_login.get_handler(), - LoginEvent( - error="", - error_description="", - page=self, - control=self, - target="page", - name="on_login", - data="", - ), - ) - return self.__authorization - - async def _authorize_callback_async(self, data: str) -> None: - await self.on_event_async(Event("page", "authorize", json.dumps(data))) - - async def __on_authorize_async(self, e) -> None: - assert self.__authorization - d = json.loads(e.data) - state = d.get("state") - assert state == self.__authorization.state - - if not self.web: - if self.platform in ["ios", "android"]: - # close web view on mobile - self.close_in_app_web_view() - else: - # activate desktop window - self.window.to_front() - login_evt = LoginEvent( - error=d.get("error"), - error_description=d.get("error_description"), - page=self, - control=self, - target="page", - name="on_login", - data="", - ) - if not login_evt.error: - # perform token request - - code = d.get("code") - assert code not in [None, ""] - try: - await self.__authorization.request_token_async(code) - except Exception as ex: - login_evt.error = str(ex) - self.run_task( - self.__on_login.get_handler(), - login_evt, - ) - - def logout(self) -> None: - self.__authorization = None - self.run_task( - self.__on_logout.get_handler(), - ControlEvent( - target="page", name="logout", data="", control=self, page=self - ), - ) - - def _send_command( - self, - name: str, - values: Optional[List[str]] = None, - attrs: Optional[Dict[str, str]] = None, - ): - return self.__conn.send_command( - self._session_id, - Command( - indent=0, - name=name, - values=values if values is not None else [], - attrs=attrs or {}, - ), - ) - - def set_clipboard(self, value: str, wait_timeout: Optional[float] = 10) -> None: - self._invoke_method("setClipboard", {"data": value}, wait_timeout=wait_timeout) - - def get_clipboard(self, wait_timeout: Optional[float] = 10) -> Optional[str]: - return self._invoke_method( - "getClipboard", wait_for_result=True, wait_timeout=wait_timeout - ) - - async def get_clipboard_async( - self, wait_timeout: Optional[float] = 10 - ) -> Optional[str]: - return await self._invoke_method_async( - "getClipboard", wait_for_result=True, wait_timeout=wait_timeout - ) - - def launch_url( - self, - url: str, - web_window_name: Optional[str] = None, - web_popup_window: Optional[bool] = False, - window_width: Optional[int] = None, - window_height: Optional[int] = None, - ) -> None: - args = {"url": url} - if web_window_name: - args["web_window_name"] = web_window_name - if web_popup_window: - args["web_popup_window"] = str(web_popup_window) - if window_width: - args["window_width"] = str(window_width) - if window_height: - args["window_height"] = str(window_height) - self._invoke_method("launchUrl", args) - - def can_launch_url(self, url: str) -> bool: - args = {"url": url} - return self._invoke_method("canLaunchUrl", args, wait_for_result=True) == "true" - - async def can_launch_url_async(self, url: str) -> bool: - args = {"url": url} - return ( - await self._invoke_method_async("canLaunchUrl", args, wait_for_result=True) - == "true" - ) - - def close_in_app_web_view(self) -> None: - self._invoke_method("closeInAppWebView") - - def scroll_to( - self, - offset: Optional[float] = None, - delta: Optional[float] = None, - key: Optional[str] = None, - duration: Optional[int] = None, - curve: Optional[AnimationCurve] = None, - ) -> None: - self.__default_view.scroll_to( - offset=offset, delta=delta, key=key, duration=duration, curve=curve - ) - - def _invoke_method( - self, - method_name: str, - arguments: Optional[Dict[str, str]] = None, - control_id: Optional[str] = "", - wait_for_result: Optional[bool] = False, - wait_timeout: Optional[float] = 5, - ) -> Optional[str]: - method_id = uuid.uuid4().hex - - # register callback - evt: Optional[threading.Event] = None - if wait_for_result: - evt = threading.Event() - self.__method_calls[method_id] = evt - - # call method - result = self._send_command( - "invokeMethod", values=[method_id, method_name, control_id], attrs=arguments - ) - - if result.error != "": - if wait_for_result: - del self.__method_calls[method_id] - raise Exception(result.error) - if not wait_for_result: - return - assert evt is not None - - if not evt.wait(wait_timeout): - del self.__method_calls[method_id] - raise TimeoutError( - f"Timeout waiting for invokeMethod {method_name}({arguments}) call" - ) - - result, err = self.__method_call_results.pop(evt) - if err: - raise Exception(err) - if result is None or result == "null": - return None - return result - - async def _invoke_method_async( - self, - method_name: str, - arguments: Optional[Dict[str, str]] = None, - control_id: Optional[str] = "", - wait_for_result: Optional[bool] = False, - wait_timeout: Optional[float] = 5, - ) -> Optional[str]: - method_id = uuid.uuid4().hex - - # register callback - evt: Optional[asyncio.Event] = None - if wait_for_result: - evt = asyncio.Event() - self.__method_calls[method_id] = evt - - # call method - result = self._send_command( - "invokeMethod", values=[method_id, method_name, control_id], attrs=arguments - ) - - if result.error != "": - if wait_for_result: - del self.__method_calls[method_id] - raise Exception(result.error) - if not wait_for_result: - return - assert evt is not None - - try: - await asyncio.wait_for(evt.wait(), timeout=wait_timeout) - except TimeoutError: - del self.__method_calls[method_id] - raise TimeoutError( - f"Timeout waiting for invokeMethod {method_name}({arguments}) call" - ) - - result, err = self.__method_call_results.pop(evt) - if err: - raise Exception(err) - if result == "null": - return None - return result - - def __on_invoke_method_result(self, e) -> None: - d = json.loads(e.data) - result = InvokeMethodResults(**d) - evt = self.__method_calls.pop(result.method_id, None) - if evt is None: - return - self.__method_call_results[evt] = (result.result, result.error) - evt.set() - - def open(self, control: Control) -> None: - if not hasattr(control, "open"): - raise ValueError(f"{control.__class__.__qualname__} has no open attribute") - - control.open = True - - if isinstance(control, NavigationDrawer): - if control.position == NavigationDrawerPosition.END: - if self.end_drawer != control: - self.end_drawer = control - self.update() - else: - if self.drawer != control: - self.drawer = control - self.update() - else: - if control not in self.__offstage.controls: - self.__offstage.controls.append(control) - self.__offstage.update() - - control.update() - - @staticmethod - def close(control: Control) -> None: - if hasattr(control, "open"): - control.open = False - control.update() - else: - raise ValueError(f"{control.__class__.__qualname__} has no open attribute") - - # query - @property - def query(self) -> QueryString: - return self.__query - - # url - @property - def url(self) -> Optional[str]: - return self.__conn.page_url - - # name - @property - def name(self) -> str: - return self.__conn.page_name - - # connection - @property - def connection(self) -> Optional[Connection]: - return self.__conn - - # snapshot - @property - def snapshot(self) -> Dict[str, Dict[str, Any]]: - return self.__snapshot - - # loop - @property - def loop(self) -> asyncio.AbstractEventLoop: - return self.__loop - - # executor - @property - def executor(self) -> Optional[ThreadPoolExecutor]: - return self.__executor - - # expires_at - @property - def expires_at(self) -> Optional[datetime]: - return self.__expires_at - - # index - @property - def index(self) -> "Dict[str, Page]": - return self._index - - # session_id - @property - def session_id(self) -> Any: - return self._session_id - - # auth - @property - def auth(self) -> Optional[Authorization]: - return self.__authorization - - # pubsub - @property - def pubsub(self) -> PubSubClient: - return self.__pubsub - - # overlay - @property - def overlay(self) -> List[Control]: - return self.__offstage.controls - - # title - @property - def title(self) -> str: - return self._get_attr("title") - - @title.setter - def title(self, value: str): - self._set_attr("title", value) - - # route - @property - def route(self) -> str: - return self._get_attr("route") - - @route.setter - def route(self, value: str): - self._set_attr("route", value) - - # pwa - @property - def pwa(self) -> bool: - return self._get_attr("pwa", data_type="bool", def_value=False) - - # web - @property - def web(self) -> bool: - return cast(bool, self._get_attr("web", data_type="bool", def_value=False)) - - # debug - @property - def debug(self) -> bool: - return cast(bool, self._get_attr("debug", data_type="bool", def_value=False)) - - # platform - @property - def platform(self) -> PagePlatform: - return PagePlatform(self._get_attr("platform")) - - @platform.setter - def platform(self, value: PagePlatform): - self._set_attr( - "platform", value.value if isinstance(value, PagePlatform) else value - ) - - # platform_brightness - @property - def platform_brightness(self) -> Brightness: - brightness = self._get_attr("platformBrightness") - assert brightness - return Brightness(brightness) - - # media - @property - def media(self) -> Optional["PageMediaData"]: - m = self._get_attr("media") - if not isinstance(m, str): - return None - d = json.loads(m) - return PageMediaData(**d) - - # client_ip - @property - def client_ip(self): - return self._get_attr("clientIP") - - # client_user_agent - @property - def client_user_agent(self): - return self._get_attr("clientUserAgent") - - # fonts - @property - def fonts(self) -> Optional[Dict[str, str]]: - return self.__fonts - - @fonts.setter - def fonts(self, value: Optional[Dict[str, str]]): - self.__fonts = value - - # views - @property - def views(self) -> List[View]: - return self.__views - - # controls - @property - def controls(self) -> Optional[List[Control]]: - return self.__default_view.controls - - @controls.setter - def controls(self, value: Optional[Sequence[Control]]): - self.__default_view.controls = list(value) if value is not None else [] - - # appbar - @property - def appbar(self) -> Union[AppBar, CupertinoAppBar, None]: - return self.__default_view.appbar - - @appbar.setter - def appbar(self, value: Union[AppBar, CupertinoAppBar, None]): - self.__default_view.appbar = value - - # bottom_appbar - @property - def bottom_appbar(self) -> Optional[BottomAppBar]: - return self.__default_view.bottom_appbar - - @bottom_appbar.setter - def bottom_appbar(self, value: Optional[BottomAppBar]): - self.__default_view.bottom_appbar = value - - # navigation_bar - @property - def navigation_bar(self) -> Optional[Union[NavigationBar, CupertinoNavigationBar]]: - return self.__default_view.navigation_bar - - @navigation_bar.setter - def navigation_bar( - self, - value: Optional[Union[NavigationBar, CupertinoNavigationBar]], - ): - self.__default_view.navigation_bar = value - - # drawer - @property - def drawer(self) -> Optional[NavigationDrawer]: - return self.__default_view.drawer - - @drawer.setter - def drawer(self, value: Optional[NavigationDrawer]): - self.__default_view.drawer = value - - # end_drawer - @property - def end_drawer(self) -> Optional[NavigationDrawer]: - return self.__default_view.end_drawer - - @end_drawer.setter - def end_drawer(self, value: Optional[NavigationDrawer]): - self.__default_view.end_drawer = value - - # decoration - @property - def decoration(self) -> Optional[BoxDecoration]: - return self.__default_view.decoration - - @decoration.setter - def decoration(self, value: Optional[BoxDecoration]): - self.__default_view.decoration = value - - # foreground_decoration - @property - def foreground_decoration(self) -> Optional[BoxDecoration]: - return self.__default_view.foreground_decoration - - @foreground_decoration.setter - def foreground_decoration(self, value: Optional[BoxDecoration]): - self.__default_view.foreground_decoration = value - - # floating_action_button - @property - def floating_action_button(self) -> Optional[FloatingActionButton]: - return self.__default_view.floating_action_button - - @floating_action_button.setter - def floating_action_button(self, value: Optional[FloatingActionButton]): - self.__default_view.floating_action_button = value - - # floating_action_button_location - @property - def floating_action_button_location( - self, - ) -> Union[FloatingActionButtonLocation, OffsetValue]: - return self.__default_view.floating_action_button_location - - @floating_action_button_location.setter - def floating_action_button_location( - self, value: Union[FloatingActionButtonLocation, OffsetValue] - ): - self.__default_view.floating_action_button_location = value - - # horizontal_alignment - @property - def horizontal_alignment(self) -> CrossAxisAlignment: - return self.__default_view.horizontal_alignment - - @horizontal_alignment.setter - def horizontal_alignment(self, value: CrossAxisAlignment): - self.__default_view.horizontal_alignment = value - - # vertical_alignment - @property - def vertical_alignment(self) -> MainAxisAlignment: - return self.__default_view.vertical_alignment - - @vertical_alignment.setter - def vertical_alignment(self, value: MainAxisAlignment): - self.__default_view.vertical_alignment = value - - # window - @property - def window(self) -> Window: - return self.__window - - # browser_context_menu - @property - def browser_context_menu(self) -> BrowserContextMenu: - return self.__browser_context_menu - - # spacing - @property - def spacing(self) -> OptionalNumber: - return self.__default_view.spacing - - @spacing.setter - def spacing(self, value: OptionalNumber): - self.__default_view.spacing = value - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__default_view.padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__default_view.padding = value - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__default_view.bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__default_view.bgcolor = value - - # scroll - @property - def scroll(self) -> Optional[ScrollMode]: - return self.__default_view.scroll - - @scroll.setter - def scroll(self, value: Optional[ScrollMode]): - self.__default_view.scroll = value - - # auto_scroll - @property - def auto_scroll(self) -> bool: - return self.__default_view.auto_scroll - - @auto_scroll.setter - def auto_scroll(self, value: Optional[bool]): - self.__default_view.auto_scroll = value - - # client_storage - @property - def client_storage(self) -> ClientStorage: - return self.__client_storage - - # session_storage - @property - def session(self) -> SessionStorage: - return self.__session_storage - - # theme_mode - @property - def theme_mode(self) -> Optional[ThemeMode]: - return self.__theme_mode - - @theme_mode.setter - def theme_mode(self, value: Optional[ThemeMode]): - self.__theme_mode = value - self._set_attr( - "themeMode", value.value if isinstance(value, ThemeMode) else value - ) - - # theme - @property - def theme(self) -> Optional[Theme]: - return self.__theme - - @theme.setter - def theme(self, value: Optional[Theme]): - self.__theme = value - - # dark_theme - @property - def dark_theme(self) -> Optional[Theme]: - return self.__dark_theme - - @dark_theme.setter - def dark_theme(self, value: Optional[Theme]): - self.__dark_theme = value - - # locale_configuration - @property - def locale_configuration(self) -> Optional[LocaleConfiguration]: - return self.__locale_configuration - - @locale_configuration.setter - def locale_configuration(self, value: Optional[LocaleConfiguration]): - self.__locale_configuration = value - - # rtl - @property - def rtl(self) -> bool: - return self._get_attr("rtl", data_type="bool", def_value=False) - - @rtl.setter - def rtl(self, value: Optional[bool]): - self._set_attr("rtl", value) - - # show_semantics_debugger - @property - def show_semantics_debugger(self) -> bool: - return self._get_attr( - "showSemanticsDebugger", data_type="bool", def_value=False - ) - - @show_semantics_debugger.setter - def show_semantics_debugger(self, value: Optional[bool]): - self._set_attr("showSemanticsDebugger", value) - - # width - @property - def width(self) -> OptionalNumber: - w = self._get_attr("width") - return float(w) if w else 0 - - # height - @property - def height(self) -> OptionalNumber: - h = self._get_attr("height") - return float(h) if h else 0 - - # on_scroll_interval - @property - def on_scroll_interval(self) -> OptionalNumber: - return self.__default_view.on_scroll_interval - - @on_scroll_interval.setter - def on_scroll_interval(self, value: OptionalNumber): - self.__default_view.on_scroll_interval = value - - # on_close - @property - def on_close(self) -> OptionalControlEventCallable: - return self.__on_close.handler - - @on_close.setter - def on_close(self, handler: OptionalControlEventCallable): - self.__on_close.handler = handler - - @property - def on_resized(self) -> OptionalEventCallable["WindowResizeEvent"]: - return self.__on_resized.handler - - @on_resized.setter - def on_resized(self, handler: OptionalEventCallable["WindowResizeEvent"]): - self.__on_resized.handler = handler - - # on_platform_brightness_change - @property - def on_platform_brightness_change(self) -> OptionalControlEventCallable: - return self.__on_platform_brightness_change.handler - - @on_platform_brightness_change.setter - def on_platform_brightness_change(self, handler: OptionalControlEventCallable): - self.__on_platform_brightness_change.handler = handler - - # on_app_lifecycle_change - @property - def on_app_lifecycle_state_change( - self, - ) -> OptionalEventCallable["AppLifecycleStateChangeEvent"]: - return self.__on_app_lifecycle_state_change.handler - - @on_app_lifecycle_state_change.setter - def on_app_lifecycle_state_change( - self, handler: OptionalEventCallable["AppLifecycleStateChangeEvent"] - ): - self.__on_app_lifecycle_state_change.handler = handler - - # on_route_change - @property - def on_route_change(self) -> OptionalEventCallable["RouteChangeEvent"]: - return self.__on_route_change.handler - - @on_route_change.setter - def on_route_change(self, handler: OptionalEventCallable["RouteChangeEvent"]): - self.__on_route_change.handler = handler - - # on_view_pop - @property - def on_view_pop(self) -> OptionalEventCallable["ViewPopEvent"]: - return self.__on_view_pop.handler - - @on_view_pop.setter - def on_view_pop(self, handler: OptionalEventCallable["ViewPopEvent"]): - self.__on_view_pop.handler = handler - - # on_keyboard_event - @property - def on_keyboard_event(self) -> OptionalEventCallable["KeyboardEvent"]: - return self.__on_keyboard_event.handler - - @on_keyboard_event.setter - def on_keyboard_event(self, handler: OptionalEventCallable["KeyboardEvent"]): - self.__on_keyboard_event.handler = handler - self._set_attr("onKeyboardEvent", True if handler else None) - - # on_media_change - @property - def on_media_change(self) -> OptionalEventCallable["PageMediaData"]: - return self.__on_page_media_change_event.handler - - @on_media_change.setter - def on_media_change(self, handler: OptionalEventCallable["PageMediaData"]): - self.__on_page_media_change_event.handler = handler - - # on_connect - @property - def on_connect(self) -> OptionalControlEventCallable: - return self.__on_connect.handler - - @on_connect.setter - def on_connect(self, handler: OptionalControlEventCallable): - self.__on_connect.handler = handler - - # on_disconnect - @property - def on_disconnect(self) -> OptionalControlEventCallable: - return self.__on_disconnect.handler - - @on_disconnect.setter - def on_disconnect(self, handler: OptionalControlEventCallable): - self.__on_disconnect.handler = handler - - # on_login - @property - def on_login(self) -> OptionalEventCallable["LoginEvent"]: - return self.__on_login.handler - - @on_login.setter - def on_login(self, handler: OptionalEventCallable["LoginEvent"]): - self.__on_login.handler = handler - - # on_logout - @property - def on_logout(self) -> OptionalControlEventCallable: - return self.__on_logout.handler - - @on_logout.setter - def on_logout(self, handler: OptionalControlEventCallable): - self.__on_logout.handler = handler - - # on_error - @property - def on_error(self) -> OptionalControlEventCallable: - return self.__on_error.handler - - @on_error.setter - def on_error(self, handler: OptionalControlEventCallable): - self.__on_error.handler = handler - - # on_scroll - @property - def on_scroll(self): - return self.__default_view.on_scroll - - @on_scroll.setter - def on_scroll(self, handler: Optional[Callable[[OnScrollEvent], None]]): - self.__default_view.on_scroll = handler - - # Magic methods - def __contains__(self, item: Control) -> bool: - return item in self._controls - - -class Offstage(Control): - def __init__( - self, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ) -> None: - Control.__init__( - self, - visible=visible, - disabled=disabled, - data=data, - ) - - self.__controls: List[Control] = [] - self.__banner = None - self.__snack_bar = None - self.__dialog = None - self.__bottom_sheet = None - self.__splash = None - - def _get_control_name(self): - return "offstage" - - def _get_children(self): - return self.__controls - - # controls - @property - def controls(self) -> List[Control]: - return self.__controls - - # splash - @property - def splash(self) -> Optional[Control]: - return self.__splash - - @splash.setter - def splash(self, value: Optional[Control]): - self.__splash = value - if value is not None: - self.__controls.append(value) - - # banner - @property - def banner(self) -> Optional[Banner]: - return self.__banner - - @banner.setter - def banner(self, value: Optional[Banner]): - self.__banner = value - if value is not None: - self.__controls.append(value) - - # snack_bar - @property - def snack_bar(self) -> Optional[SnackBar]: - return self.__snack_bar - - @snack_bar.setter - def snack_bar(self, value: Optional[SnackBar]): - self.__snack_bar = value - if value is not None: - self.__controls.append(value) - - # dialog - @property - def dialog(self) -> Optional[Union[AlertDialog, CupertinoAlertDialog]]: - return self.__dialog - - @dialog.setter - def dialog(self, value: Optional[Union[AlertDialog, CupertinoAlertDialog]]): - self.__dialog = value - if value is not None: - self.__controls.append(value) - - # bottom_sheet - @property - def bottom_sheet(self) -> Optional[Union[BottomSheet, CupertinoBottomSheet]]: - return self.__bottom_sheet - - @bottom_sheet.setter - def bottom_sheet(self, value: Optional[Union[BottomSheet, CupertinoBottomSheet]]): - self.__bottom_sheet = value - if value is not None: - self.__controls.append(value) - - -@dataclass -class RouteChangeEvent(ControlEvent): - route: str - - -@dataclass -class ViewPopEvent(ControlEvent): - view: View - - -@dataclass -class KeyboardEvent(ControlEvent): - key: str - shift: bool - ctrl: bool - alt: bool - meta: bool - - -class LoginEvent(ControlEvent): - def __init__( - self, - error: str, - error_description: str, - target: str, - name: str, - data: str, - control, - page, - ) -> None: - super().__init__(target, name, data, control, page) - - self.error = error - self.error_description = error_description - - -@dataclass -class InvokeMethodResults: - method_id: str - result: Optional[str] - error: Optional[str] - - -class PageMediaData(ControlEvent): - def __init__(self, padding, view_padding, view_insets) -> None: - self.padding = Padding( - left=padding["left"], - top=padding["top"], - right=padding["right"], - bottom=padding["bottom"], - ) - self.view_padding = Padding( - left=view_padding["left"], - top=view_padding["top"], - right=view_padding["right"], - bottom=view_padding["bottom"], - ) - self.view_insets = Padding( - left=view_insets["left"], - top=view_insets["top"], - right=view_insets["right"], - bottom=view_insets["bottom"], - ) - - def __str__(self) -> str: - return f"PageMediaData(padding={self.padding}, view_padding={self.view_padding}, view_insets={self.view_insets})" - - -class AppLifecycleStateChangeEvent(ControlEvent): - def __init__(self, e: ControlEvent) -> None: - super().__init__(e.target, e.name, e.data, e.control, e.page) - self.state = AppLifecycleState(e.data) - - -class WindowEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - self.type = WindowEventType(e.data) - - -class WindowResizeEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - size = json.loads(e.data) - self.width: float = size["width"] - self.height: float = size["height"] diff --git a/sdk/python/packages/flet/src/flet/core/pagelet.py b/sdk/python/packages/flet/src/flet/core/pagelet.py deleted file mode 100644 index ee88146b3..000000000 --- a/sdk/python/packages/flet/src/flet/core/pagelet.py +++ /dev/null @@ -1,314 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.app_bar import AppBar -from flet.core.badge import BadgeValue -from flet.core.bottom_app_bar import BottomAppBar -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.cupertino_app_bar import CupertinoAppBar -from flet.core.cupertino_navigation_bar import CupertinoNavigationBar -from flet.core.floating_action_button import FloatingActionButton -from flet.core.navigation_bar import NavigationBar -from flet.core.navigation_drawer import NavigationDrawer -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - FloatingActionButtonLocation, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class Pagelet(ConstrainedControl, AdaptiveControl): - """ - Pagelet implements the basic Material Design visual layout structure. - - Use it for projects that require "page within a page" layouts with its own AppBar, BottomBar, Drawer, such as demos and galleries. - - Example: - ``` - import flet as ft - - - def main(page: ft.Page): - page.add( - ft.Pagelet( - appbar=ft.CupertinoAppBar(middle=ft.Text("AppBar title")), - content=ft.Text("This is pagelet"), - ) - ) - - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/pagelet - """ - - def __init__( - self, - content: Control, - appbar: Union[AppBar, CupertinoAppBar, None] = None, - navigation_bar: Union[NavigationBar, CupertinoNavigationBar, None] = None, - bottom_app_bar: Optional[BottomAppBar] = None, - bottom_sheet: Optional[Control] = None, - drawer: Optional[NavigationDrawer] = None, - end_drawer: Optional[NavigationDrawer] = None, - floating_action_button: Optional[FloatingActionButton] = None, - floating_action_button_location: Union[ - FloatingActionButtonLocation, OffsetValue - ] = None, - bgcolor: Optional[ColorValue] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - key: Optional[str] = None, - # - # AdaptiveControl - # - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.content = content - self.appbar = appbar - self.bgcolor = bgcolor - self.navigation_bar = navigation_bar - self.bottom_appbar = bottom_app_bar - self.bottom_sheet = bottom_sheet - self.drawer = drawer - self.end_drawer = end_drawer - self.floating_action_button = floating_action_button - self.floating_action_button_location = floating_action_button_location - - def _get_control_name(self): - return "pagelet" - - def _get_children(self): - self.__content._set_attr_internal("n", "content") - children = [self.__content] - if self.__appbar: - self.__appbar._set_attr_internal("n", "appbar") - children.append(self.__appbar) - if self.__navigation_bar: - self.__navigation_bar._set_attr_internal("n", "navigationbar") - children.append(self.__navigation_bar) - if self.__bottom_appbar: - self.__bottom_appbar._set_attr_internal("n", "bottomappbar") - children.append(self.__bottom_appbar) - if self.__bottom_sheet: - self.__bottom_sheet._set_attr_internal("n", "bottomsheet") - children.append(self.__bottom_sheet) - if self.__drawer: - self.__drawer._set_attr_internal("n", "drawer") - children.append(self.__drawer) - if self.__end_drawer: - self.__end_drawer._set_attr_internal("n", "enddrawer") - children.append(self.__end_drawer) - if self.__floating_action_button: - self.__floating_action_button._set_attr_internal( - "n", "floatingactionbutton" - ) - children.append(self.__floating_action_button) - return children - - def before_update(self): - super().before_update() - assert self.__content.visible, "content must be visible" - - # Drawer - # - def show_drawer(self, drawer: NavigationDrawer): - self.drawer = drawer - self.drawer.open = True - self.update() - - def close_drawer(self): - if self.drawer is not None: - self.drawer.open = False - self.update() - - # End_drawer - # - def show_end_drawer(self, end_drawer: NavigationDrawer): - self.end_drawer = end_drawer - self.end_drawer.open = True - self.update() - - def close_end_drawer(self): - if self.end_drawer is not None: - self.end_drawer.open = False - self.update() - - # appbar - @property - def appbar(self) -> Union[AppBar, CupertinoAppBar, None]: - return self.__appbar - - @appbar.setter - def appbar(self, value: Union[AppBar, CupertinoAppBar, None]): - self.__appbar = value - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # bottom_appbar - @property - def bottom_appbar(self) -> Optional[BottomAppBar]: - return self.__bottom_appbar - - @bottom_appbar.setter - def bottom_appbar(self, value: Optional[BottomAppBar]): - self.__bottom_appbar = value - - # navigation_bar - @property - def navigation_bar(self) -> Union[NavigationBar, CupertinoNavigationBar, None]: - return self.__navigation_bar - - @navigation_bar.setter - def navigation_bar( - self, - value: Union[NavigationBar, CupertinoNavigationBar, None], - ): - self.__navigation_bar = value - - # bottom_sheet - @property - def bottom_sheet(self) -> Optional[Control]: - return self.__bottom_sheet - - @bottom_sheet.setter - def bottom_sheet( - self, - value: Optional[Control], - ): - self.__bottom_sheet = value - - # drawer - @property - def drawer(self) -> Optional[NavigationDrawer]: - return self.__drawer - - @drawer.setter - def drawer(self, value: Optional[NavigationDrawer]): - self.__drawer = value - - # end_drawer - @property - def end_drawer(self) -> Optional[NavigationDrawer]: - return self.__end_drawer - - @end_drawer.setter - def end_drawer(self, value: Optional[NavigationDrawer]): - self.__end_drawer = value - - # floating_action_button - @property - def floating_action_button(self) -> Optional[FloatingActionButton]: - return self.__floating_action_button - - @floating_action_button.setter - def floating_action_button(self, value: Optional[FloatingActionButton]): - self.__floating_action_button = value - - # floating_action_button_location - @property - def floating_action_button_location( - self, - ) -> Union[FloatingActionButtonLocation, OffsetValue]: - return self.__floating_action_button_location - - @floating_action_button_location.setter - def floating_action_button_location( - self, value: Union[FloatingActionButtonLocation, OffsetValue] - ): - self.__floating_action_button_location = value - self._set_attr( - "floatingActionButtonLocation", - value.value if isinstance(value, FloatingActionButtonLocation) else value, - ) diff --git a/sdk/python/packages/flet/src/flet/core/painting.py b/sdk/python/packages/flet/src/flet/core/painting.py deleted file mode 100644 index 42e2d3af5..000000000 --- a/sdk/python/packages/flet/src/flet/core/painting.py +++ /dev/null @@ -1,75 +0,0 @@ -import math -from dataclasses import dataclass, field -from enum import Enum -from typing import List, Optional, Tuple, Union - -from flet.core.blur import Blur -from flet.core.gradients import GradientTileMode -from flet.core.types import BlendMode, ColorValue, OffsetValue, StrokeCap - - -class StrokeJoin(Enum): - MITER = "miter" - ROUND = "round" - BEVEL = "bevel" - - -class PaintingStyle(Enum): - FILL = "fill" - STROKE = "stroke" - - -@dataclass -class PaintGradient: - pass - - -@dataclass -class PaintLinearGradient(PaintGradient): - begin: Optional[OffsetValue] - end: Optional[OffsetValue] - colors: List[str] - color_stops: Optional[List[float]] = None - tile_mode: GradientTileMode = field(default=GradientTileMode.CLAMP) - type: str = field(default="linear") - - -@dataclass -class PaintRadialGradient(PaintGradient): - center: Optional[OffsetValue] - radius: Union[float, int] - colors: List[str] - color_stops: Optional[List[float]] = None - tile_mode: GradientTileMode = field(default=GradientTileMode.CLAMP) - focal: Optional[OffsetValue] = None - focal_radius: Union[float, int] = field(default=0.0) - type: str = field(default="radial") - - -@dataclass -class PaintSweepGradient(PaintGradient): - center: Optional[OffsetValue] - colors: List[str] - color_stops: Optional[List[float]] = None - tile_mode: GradientTileMode = field(default=GradientTileMode.CLAMP) - start_angle: float = field(default=0.0) - end_angle: float = field(default=math.pi * 2) - rotation: Union[None, float, int] = None - type: str = field(default="sweep") - - -@dataclass -class Paint: - color: Optional[ColorValue] = None - blend_mode: Optional[BlendMode] = None - blur_image: Union[ - None, float, int, Tuple[Union[float, int], Union[float, int]], Blur - ] = None - anti_alias: Optional[bool] = None - gradient: Optional[PaintGradient] = None - stroke_cap: Optional[StrokeCap] = None - stroke_join: Optional[StrokeJoin] = None - stroke_miter_limit: Optional[float] = None - stroke_width: Optional[float] = None - stroke_dash_pattern: Optional[List[float]] = None - style: Optional[PaintingStyle] = None diff --git a/sdk/python/packages/flet/src/flet/core/permission_handler.py b/sdk/python/packages/flet/src/flet/core/permission_handler.py deleted file mode 100644 index d0a00e4ed..000000000 --- a/sdk/python/packages/flet/src/flet/core/permission_handler.py +++ /dev/null @@ -1,150 +0,0 @@ -from enum import Enum -from typing import Any, Optional - -from flet.core.control import Control -from flet.core.ref import Ref -from flet.utils import deprecated - - -class PermissionStatus(Enum): - GRANTED = "granted" - DENIED = "denied" - PERMANENTLY_DENIED = "permanentlyDenied" - LIMITED = "limited" - PROVISIONAL = "provisional" - RESTRICTED = "restricted" - - -class PermissionType(Enum): - ACCESS_MEDIA_LOCATION = "accessMediaLocation" - ACCESS_NOTIFICATION_POLICY = "accessNotificationPolicy" - ACTIVITY_RECOGNITION = "activityRecognition" - APP_TRACKING_TRANSPARENCY = "appTrackingTransparency" - ASSISTANT = "assistant" - AUDIO = "audio" - BACKGROUND_REFRESH = "backgroundRefresh" - BLUETOOTH = "bluetooth" - BLUETOOTH_ADVERTISE = "bluetoothAdvertise" - BLUETOOTH_CONNECT = "bluetoothConnect" - BLUETOOTH_SCAN = "bluetoothScan" - CALENDAR_FULL_ACCESS = "calendarFullAccess" - CALENDAR_WRITE_ONLY = "calendarWriteOnly" - CAMERA = "camera" - CONTACTS = "contacts" - CRITICAL_ALERTS = "criticalAlerts" - IGNORE_BATTERY_OPTIMIZATIONS = "ignoreBatteryOptimizations" - LOCATION = "location" - LOCATION_ALWAYS = "locationAlways" - LOCATION_WHEN_IN_USE = "locationWhenInUse" - MANAGE_EXTERNAL_STORAGE = "manageExternalStorage" - MEDIA_LIBRARY = "mediaLibrary" - MICROPHONE = "microphone" - NEARBY_WIFI_DEVICES = "nearbyWifiDevices" - NOTIFICATION = "notification" - PHONE = "phone" - PHOTOS = "photos" - PHOTOS_ADD_ONLY = "photosAddOnly" - REMINDERS = "reminders" - REQUEST_INSTALL_PACKAGES = "requestInstallPackages" - SCHEDULE_EXACT_ALARM = "scheduleExactAlarm" - SENSORS = "sensors" - SENSORS_ALWAYS = "sensorsAlways" - SMS = "sms" - SPEECH = "speech" - STORAGE = "storage" - SYSTEM_ALERT_WINDOW = "systemAlertWindow" - UNKNOWN = "unknown" - VIDEOS = "videos" - - -@deprecated( - reason="PermissionHandler control has been moved to a separate Python package: https://pypi.org/project/flet-permission-handler. " - + "Read more about this change in Flet blog: https://flet.dev/blog/flet-v-0-26-release-announcement", - version="0.26.0", - delete_version="0.29.0", -) -class PermissionHandler(Control): - """ - A control that allows you check and request permission from your device. - This control is non-visual and should be added to `page.overlay` list. - - ----- - - Online docs: https://flet.dev/docs/controls/permissionhandler - """ - - def __init__( - self, - # Control - # - ref: Optional[Ref] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - data=data, - ) - - def _get_control_name(self): - return "permission_handler" - - def check_permission( - self, of: PermissionType, wait_timeout: Optional[float] = 25 - ) -> Optional[PermissionStatus]: - out = self.invoke_method( - "check_permission", - {"of": of.value if isinstance(of, PermissionType) else of}, - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return PermissionStatus(out) if out is not None else None - - async def check_permission_async( - self, of: PermissionType, wait_timeout: Optional[float] = 25 - ) -> Optional[PermissionStatus]: - out = await self.invoke_method_async( - "check_permission", - {"of": of.value if isinstance(of, PermissionType) else of}, - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return PermissionStatus(out) if out is not None else None - - def request_permission( - self, of: PermissionType, wait_timeout: Optional[float] = 25 - ) -> Optional[PermissionStatus]: - out = self.invoke_method( - "request_permission", - {"of": of.value if isinstance(of, PermissionType) else of}, - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return PermissionStatus(out) if out is not None else None - - async def request_permission_async( - self, of: PermissionType, wait_timeout: Optional[float] = 25 - ) -> Optional[PermissionStatus]: - out = await self.invoke_method_async( - "request_permission", - {"of": of.value if isinstance(of, PermissionType) else of}, - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return PermissionStatus(out) if out is not None else None - - def open_app_settings(self, wait_timeout: Optional[float] = 10) -> bool: - opened = self.invoke_method( - "open_app_settings", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return opened == "true" - - async def open_app_settings_async(self, wait_timeout: Optional[float] = 10) -> bool: - opened = await self.invoke_method_async( - "open_app_settings", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return opened == "true" diff --git a/sdk/python/packages/flet/src/flet/core/placeholder.py b/sdk/python/packages/flet/src/flet/core/placeholder.py deleted file mode 100644 index e38452381..000000000 --- a/sdk/python/packages/flet/src/flet/core/placeholder.py +++ /dev/null @@ -1,160 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class Placeholder(ConstrainedControl): - """ - A placeholder box. - - ----- - - Online docs: https://flet.dev/docs/controls/placeholder - """ - - def __init__( - self, - content: Optional[Control] = None, - color: Optional[ColorValue] = None, - fallback_height: OptionalNumber = None, - fallback_width: OptionalNumber = None, - stroke_width: OptionalNumber = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - rtl=rtl, - ) - - self.content = content - self.color = color - self.fallback_height = fallback_height - self.fallback_width = fallback_width - self.stroke_width = stroke_width - - def _get_control_name(self): - return "placeholder" - - def _get_children(self): - return [self.content] if self.content is not None else [] - - # fallback_height - @property - def fallback_height(self) -> float: - return self._get_attr("fallbackHeight", data_type="float", def_value=400.0) - - @fallback_height.setter - def fallback_height(self, value: OptionalNumber): - assert value is None or value >= 0, "fallback_height cannot be negative" - self._set_attr("fallbackHeight", value) - - # fallback_width - @property - def fallback_width(self) -> float: - return self._get_attr("fallbackWidth", data_type="float", def_value=400.0) - - @fallback_width.setter - def fallback_width(self, value: OptionalNumber): - assert value is None or value >= 0, "fallback_width cannot be negative" - self._set_attr("fallbackWidth", value) - - # stroke_width - @property - def stroke_width(self) -> float: - return self._get_attr("strokeWidth", data_type="float", def_value=2.0) - - @stroke_width.setter - def stroke_width(self, value: OptionalNumber): - self._set_attr("strokeWidth", value) - - # color - @property - def color(self) -> str: - return self._get_attr("color", def_value="bluegrey700") - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value diff --git a/sdk/python/packages/flet/src/flet/core/plotly_chart.py b/sdk/python/packages/flet/src/flet/core/plotly_chart.py deleted file mode 100644 index 37c0f563b..000000000 --- a/sdk/python/packages/flet/src/flet/core/plotly_chart.py +++ /dev/null @@ -1,160 +0,0 @@ -import re -import xml.etree.ElementTree as ET -from typing import Any, Optional, Union - -from flet.core import alignment -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.container import Container -from flet.core.control import OptionalNumber -from flet.core.image import Image -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ImageFit, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - -try: - from plotly.graph_objects import Figure -except ImportError: - raise Exception('Install "plotly" Python package to use PlotlyChart control.') - - -class PlotlyChart(Container): - """ - Displays Plotly(https://plotly.com/python/) chart. - - Example: - ``` - import plotly.express as px - - import flet as ft - from flet.core.plotly_chart import PlotlyChart - - def main(page: ft.Page): - - df = px.data.gapminder().query("continent=='Oceania'") - fig = px.line(df, x="year", y="lifeExp", color="country") - - page.add(PlotlyChart(fig, expand=True)) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/plotlychart - """ - - def __init__( - self, - figure: Optional[Figure] = None, - isolated: bool = False, - original_size: bool = False, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Container.__init__( - self, - ref=ref, - key=key, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.figure = figure - self.isolated = isolated - self.original_size = original_size - - def is_isolated(self): - return self.__isolated - - def build(self): - self.alignment = alignment.center - self.__img = Image(fit=ImageFit.FILL) - self.content = self.__img - - def before_update(self): - super().before_update() - if self.__figure is not None: - svg = self.__figure.to_image(format="svg").decode("utf-8") - - if not self.__original_size: - root = ET.fromstring(svg) - w = float(re.findall(r"\d+", root.attrib["width"])[0]) - h = float(re.findall(r"\d+", root.attrib["height"])[0]) - self.__img.aspect_ratio = w / h - self.__img.src = svg - - # original_size - @property - def original_size(self) -> bool: - return self.__original_size - - @original_size.setter - def original_size(self, value: bool): - self.__original_size = value - - # isolated - @property - def isolated(self) -> bool: - return self.__isolated - - @isolated.setter - def isolated(self, value: bool): - self.__isolated = value - - # figure - @property - def figure(self) -> Optional[Figure]: - return self.__figure - - @figure.setter - def figure(self, value: Optional[Figure]): - self.__figure = value diff --git a/sdk/python/packages/flet/src/flet/core/popup_menu_button.py b/sdk/python/packages/flet/src/flet/core/popup_menu_button.py deleted file mode 100644 index dbde08f18..000000000 --- a/sdk/python/packages/flet/src/flet/core/popup_menu_button.py +++ /dev/null @@ -1,532 +0,0 @@ -from enum import Enum -from typing import Any, List, Optional, Union - -from flet.core.animation import AnimationStyle, AnimationValue -from flet.core.badge import BadgeValue -from flet.core.box import BoxConstraints -from flet.core.buttons import ButtonStyle, OutlinedBorder -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ClipBehavior, - ColorEnums, - ColorValue, - IconEnums, - IconValue, - MouseCursor, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class PopupMenuPosition(Enum): - OVER = "over" - UNDER = "under" - - -class PopupMenuItem(Control): - def __init__( - self, - text: Optional[str] = None, - icon: Optional[IconValue] = None, - checked: Optional[bool] = None, - content: Optional[Control] = None, - height: OptionalNumber = None, - padding: Optional[PaddingValue] = None, - mouse_cursor: Optional[MouseCursor] = None, - on_click: OptionalControlEventCallable = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - disabled=disabled, - data=data, - tooltip=tooltip, - badge=badge, - ) - - self.checked = checked - self.icon = icon - self.text = text - self.__content: Optional[Control] = None - self.content = content - self.on_click = on_click - self.height = height - self.padding = padding - self.mouse_cursor = mouse_cursor - - def _get_control_name(self): - return "popupmenuitem" - - def _get_children(self): - children = [] - if self.__content: - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - return children - - # checked - @property - def checked(self) -> bool: - return self._get_attr("checked", data_type="bool", def_value=False) - - @checked.setter - def checked(self, value: Optional[bool]): - self._set_attr("checked", value) - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self._get_attr("mouseCursor") - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # icon - @property - def icon(self) -> Optional[IconValue]: - return self.__icon - - @icon.setter - def icon(self, value: Optional[IconValue]): - self.__icon = value - self._set_enum_attr("icon", value, IconEnums) - - # text - @property - def text(self) -> Optional[str]: - return self._get_attr("text") - - @text.setter - def text(self, value: Optional[str]): - self._set_attr("text", value) - - # height - @property - def height(self) -> float: - return self._get_attr("height", data_type="float", def_value=48.0) - - @height.setter - def height(self, value: OptionalNumber): - self._set_attr("height", value) - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) - - -class PopupMenuButton(ConstrainedControl): - """ - An icon button which displays a menu when clicked. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - def check_item_clicked(e): - e.control.checked = not e.control.checked - page.update() - - pb = ft.PopupMenuButton( - items=[ - ft.PopupMenuItem(text="Item 1"), - ft.PopupMenuItem(icon=ft.icons.POWER_INPUT, text="Check power"), - ft.PopupMenuItem( - content=ft.Row( - [ - ft.Icon(ft.icons.HOURGLASS_TOP_OUTLINED), - ft.Text("Item with a custom content"), - ] - ), - on_click=lambda _: print("Button with a custom content clicked!"), - ), - ft.PopupMenuItem(), # divider - ft.PopupMenuItem( - text="Checked item", checked=False, on_click=check_item_clicked - ), - ] - ) - page.add(pb) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/popupmenubutton - """ - - def __init__( - self, - content: Optional[Control] = None, - items: Optional[List[PopupMenuItem]] = None, - icon: Optional[IconValue] = None, - bgcolor: Optional[ColorValue] = None, - icon_color: Optional[ColorValue] = None, - shadow_color: Optional[ColorValue] = None, - surface_tint_color: Optional[ColorValue] = None, - icon_size: OptionalNumber = None, - splash_radius: OptionalNumber = None, - elevation: OptionalNumber = None, - menu_position: Optional[PopupMenuPosition] = None, - clip_behavior: Optional[ClipBehavior] = None, - enable_feedback: Optional[bool] = None, - shape: Optional[OutlinedBorder] = None, - padding: Optional[PaddingValue] = None, - menu_padding: Optional[PaddingValue] = None, - style: Optional[ButtonStyle] = None, - popup_animation_style: Optional[AnimationStyle] = None, - size_constraints: Optional[BoxConstraints] = None, - on_open: OptionalControlEventCallable = None, - on_cancel: OptionalControlEventCallable = None, - on_select: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - visible=visible, - disabled=disabled, - data=data, - ) - - self.items = items - self.icon = icon - self.on_cancel = on_cancel - self.on_open = on_open - self.shape = shape - self.padding = padding - self.menu_padding = menu_padding - self.clip_behavior = clip_behavior - self.bgcolor = bgcolor - self.icon_color = icon_color - self.shadow_color = shadow_color - self.surface_tint_color = surface_tint_color - self.splash_radius = splash_radius - self.icon_size = icon_size - self.elevation = elevation - self.enable_feedback = enable_feedback - self.content = content - self.menu_position = menu_position - self.style = style - self.popup_animation_style = popup_animation_style - self.size_constraints = size_constraints - self.on_select = on_select - - def _get_control_name(self): - return "popupmenubutton" - - def _get_children(self): - children = [] - if self.__content: - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - return children + self.__items - - def __contains__(self, item): - return item in self.__items - - def before_update(self): - super().before_update() - self._set_attr_json("shape", self.__shape) - self._set_attr_json("padding", self.__padding) - self._set_attr_json("menuPadding", self.__menu_padding) - self._set_attr_json("style", self.__style) - self._set_attr_json("popupAnimationStyle", self.__popup_animation_style) - self._set_attr_json("sizeConstraints", self.__size_constraints) - - # items - @property - def items(self) -> Optional[List[PopupMenuItem]]: - return self.__items - - @items.setter - def items(self, value: Optional[List[PopupMenuItem]]): - self.__items = value if value is not None else [] - - # shape - @property - def shape(self) -> Optional[OutlinedBorder]: - return self.__shape - - @shape.setter - def shape(self, value: Optional[OutlinedBorder]): - self.__shape = value - - # size_constraints - @property - def size_constraints(self) -> Optional[BoxConstraints]: - return self.__size_constraints - - @size_constraints.setter - def size_constraints(self, value: Optional[BoxConstraints]): - self.__size_constraints = value - - # menu_padding - @property - def menu_padding(self) -> Optional[PaddingValue]: - return self.__menu_padding - - @menu_padding.setter - def menu_padding(self, value: Optional[PaddingValue]): - self.__menu_padding = value - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # icon - @property - def icon(self) -> Optional[IconValue]: - return self.__icon - - @icon.setter - def icon(self, value: Optional[IconValue]): - self.__icon = value - self._set_enum_attr("icon", value, IconEnums) - - # icon_color - @property - def icon_color(self) -> Optional[ColorValue]: - return self.__icon_color - - @icon_color.setter - def icon_color(self, value: Optional[ColorValue]): - self.__icon_color = value - self._set_enum_attr("iconColor", value, ColorEnums) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # shadow_color - @property - def shadow_color(self) -> Optional[ColorValue]: - return self.__shadow_color - - @shadow_color.setter - def shadow_color(self, value: Optional[ColorValue]): - self.__shadow_color = value - self._set_enum_attr("shadowColor", value, ColorEnums) - - # surface_tint_color - @property - def surface_tint_color(self) -> Optional[ColorValue]: - return self.__surface_tint_color - - @surface_tint_color.setter - def surface_tint_color(self, value: Optional[ColorValue]): - self.__surface_tint_color = value - self._set_enum_attr("surfaceTintColor", value, ColorEnums) - - # icon_size - @property - def icon_size(self) -> OptionalNumber: - return self._get_attr("iconSize", data_type="float") - - @icon_size.setter - def icon_size(self, value: OptionalNumber): - self._set_attr("iconSize", value) - - # enable_feedback - @property - def enable_feedback(self) -> bool: - return self._get_attr("enableFeedback", data_type="bool", def_value=True) - - @enable_feedback.setter - def enable_feedback(self, value: Optional[bool]): - self._set_attr("enableFeedback", value) - - # elevation - @property - def elevation(self) -> float: - return self._get_attr("elevation", data_type="float", def_value=8.0) - - @elevation.setter - def elevation(self, value: OptionalNumber): - self._set_attr("elevation", value) - - # splash_radius - @property - def splash_radius(self) -> OptionalNumber: - return self._get_attr("splashRadius", data_type="float") - - @splash_radius.setter - def splash_radius(self, value: OptionalNumber): - self._set_attr("splashRadius", value) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # style - @property - def style(self) -> Optional[ButtonStyle]: - return self.__style - - @style.setter - def style(self, value: Optional[ButtonStyle]): - self.__style = value - - # popup_animation_style - @property - def popup_animation_style(self) -> Optional[AnimationStyle]: - return self.__popup_animation_style - - @popup_animation_style.setter - def popup_animation_style(self, value: Optional[AnimationStyle]): - self.__popup_animation_style = value - - # menu_position - @property - def menu_position(self) -> Optional[PopupMenuPosition]: - return self.__menu_position - - @menu_position.setter - def menu_position(self, value: Optional[PopupMenuPosition]): - self.__menu_position = value - self._set_enum_attr("menuPosition", value, PopupMenuPosition) - - # clip_behavior - @property - def clip_behavior(self) -> ClipBehavior: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: ClipBehavior): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # on_cancel - @property - def on_cancel(self) -> OptionalControlEventCallable: - return self._get_event_handler("cancel") - - @on_cancel.setter - def on_cancel(self, handler: OptionalControlEventCallable): - self._add_event_handler("cancel", handler) - - # on_open - @property - def on_open(self) -> OptionalControlEventCallable: - return self._get_event_handler("open") - - @on_open.setter - def on_open(self, handler: OptionalControlEventCallable): - self._add_event_handler("open", handler) - - # on_select - @property - def on_select(self) -> OptionalControlEventCallable: - return self._get_event_handler("select") - - @on_select.setter - def on_select(self, handler: OptionalControlEventCallable): - self._add_event_handler("select", handler) diff --git a/sdk/python/packages/flet/src/flet/core/progress_bar.py b/sdk/python/packages/flet/src/flet/core/progress_bar.py deleted file mode 100644 index 9591b1a67..000000000 --- a/sdk/python/packages/flet/src/flet/core/progress_bar.py +++ /dev/null @@ -1,269 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - BorderRadiusValue, - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) -from flet.utils.deprecated import deprecated_property - - -class ProgressBar(ConstrainedControl): - """ - A material design linear progress indicator, also known as a progress bar. - - A control that shows progress along a line. - - Example: - - ``` - from time import sleep - - import flet as ft - - def main(page: ft.Page): - pb = ft.ProgressBar(width=400) - - page.add( - ft.Text("Linear progress indicator", style="headlineSmall"), - ft.Column([ ft.Text("Doing something..."), pb]), - ft.Text("Indeterminate progress bar", style="headlineSmall"), - ft.ProgressBar(width=400, color="amber", bgcolor="#eeeeee"), - ) - - for i in range(0, 101): - pb.value = i * 0.01 - sleep(0.1) - page.update() - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/progressbar - """ - - def __init__( - self, - value: OptionalNumber = None, - bar_height: OptionalNumber = None, - color: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, - border_radius: Optional[BorderRadiusValue] = None, - semantics_label: Optional[str] = None, - semantics_value: OptionalNumber = None, - stop_indicator_color: Optional[ColorValue] = None, - stop_indicator_radius: OptionalNumber = None, - track_gap: OptionalNumber = None, - year_2023: Optional[bool] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - self.value = value - self.bar_height = bar_height - self.color = color - self.bgcolor = bgcolor - self.border_radius = border_radius - self.semantics_label = semantics_label - self.semantics_value = semantics_value - self.stop_indicator_color = stop_indicator_color - self.stop_indicator_radius = stop_indicator_radius - self.track_gap = track_gap - self.year_2023 = year_2023 - - def _get_control_name(self): - return "progressbar" - - def before_update(self): - super().before_update() - self._set_attr_json("borderRadius", self.__border_radius) - - # value - @property - def value(self) -> OptionalNumber: - return self._get_attr("value", data_type="float") - - @value.setter - def value(self, value: OptionalNumber): - assert value is None or value >= 0, "value cannot be negative" - self._set_attr("value", value) - - # bar_height - @property - def bar_height(self) -> OptionalNumber: - return self._get_attr("barHeight", data_type="float") - - @bar_height.setter - def bar_height(self, value: OptionalNumber): - assert value is None or value >= 0, "bar_height cannot be negative" - self._set_attr("barHeight", value) - - # semantics_value - @property - def semantics_value(self) -> OptionalNumber: - return self._get_attr("semanticsValue", data_type="float") - - @semantics_value.setter - def semantics_value(self, value: OptionalNumber): - assert value is None or value >= 0, "semantics_value cannot be negative" - self._set_attr("semanticsValue", value) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # semantics_label - @property - def semantics_label(self) -> Optional[str]: - return self._get_attr("semanticsLabel") - - @semantics_label.setter - def semantics_label(self, value: Optional[str]): - self._set_attr("semanticsLabel", value) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # border_radius - @property - def border_radius(self) -> Optional[BorderRadiusValue]: - return self.__border_radius - - @border_radius.setter - def border_radius(self, value: Optional[BorderRadiusValue]): - self.__border_radius = value - - # track_gap - @property - def track_gap(self) -> OptionalNumber: - return self._get_attr("trackGap", data_type="float") - - @track_gap.setter - def track_gap(self, value: OptionalNumber): - self._set_attr("trackGap", value) - - # year_2023 - @property - def year_2023(self) -> Optional[bool]: - deprecated_property( - name="year_2023", - version="0.27.0", - delete_version=None, # not known for now - reason="Set this flag to False to opt into the 2024 Slider appearance. In the future, this flag will default to False.", - ) - return self._get_attr("year2023", data_type="bool", def_value=True) - - @year_2023.setter - def year_2023(self, value: Optional[bool]): - self._set_attr("year2023", value) - if value is not None: - deprecated_property( - name="year_2023", - version="0.27.0", - delete_version=None, # not known for now - reason="Set this flag to False to opt into the 2024 Slider appearance. In the future, this flag will default to False.", - ) - - # stop_indicator_color - @property - def stop_indicator_color(self) -> Optional[ColorValue]: - return self.__stop_indicator_color - - @stop_indicator_color.setter - def stop_indicator_color(self, value: Optional[ColorValue]): - self.__stop_indicator_color = value - self._set_enum_attr("stopIndicatorColor", value, ColorEnums) - - # stop_indicator_radius - @property - def stop_indicator_radius(self) -> OptionalNumber: - return self._get_attr("stopIndicatorRadius", data_type="float") - - @stop_indicator_radius.setter - def stop_indicator_radius(self, value: OptionalNumber): - self._set_attr("stopIndicatorRadius", value) diff --git a/sdk/python/packages/flet/src/flet/core/progress_ring.py b/sdk/python/packages/flet/src/flet/core/progress_ring.py deleted file mode 100644 index 2f7d7622a..000000000 --- a/sdk/python/packages/flet/src/flet/core/progress_ring.py +++ /dev/null @@ -1,274 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.box import BoxConstraints -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, - StrokeCap, - PaddingValue, -) -from flet.utils.deprecated import deprecated_property - - -class ProgressRing(ConstrainedControl): - """ - A material design circular progress indicator, which spins to indicate that the application is busy. - - A control that shows progress along a circle. - - Example: - - ``` - from time import sleep - import flet as ft - - def main(page: ft.Page): - pr = ft.ProgressRing(width=16, height=16, stroke_width = 2) - - page.add( - ft.Text("Circular progress indicator", style="headlineSmall"), - ft.Row([pr, ft.Text("Wait for the completion...")]), - ft.Text("Indeterminate circular progress", style="headlineSmall"), - ft.Column( - [ft.ProgressRing(), ft.Text("I'm going to run for ages...")], - horizontal_alignment=ft.CrossAxisAlignment.CENTER, - ), - ) - - for i in range(0, 101): - pr.value = i * 0.01 - sleep(0.1) - page.update() - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/progressring - """ - - def __init__( - self, - value: OptionalNumber = None, - stroke_width: OptionalNumber = None, - color: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, - stroke_align: OptionalNumber = None, - stroke_cap: Optional[StrokeCap] = None, - semantics_label: Optional[str] = None, - semantics_value: OptionalNumber = None, - track_gap: OptionalNumber = None, - size_constraints: Optional[BoxConstraints] = None, - padding: Optional[PaddingValue] = None, - year_2023: Optional[bool] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - self.value = value - self.stroke_width = stroke_width - self.color = color - self.bgcolor = bgcolor - self.semantics_label = semantics_label - self.semantics_value = semantics_value - self.stroke_align = stroke_align - self.stroke_cap = stroke_cap - self.track_gap = track_gap - self.size_constraints = size_constraints - self.padding = padding - self.year_2023 = year_2023 - - def _get_control_name(self): - return "progressring" - - def before_update(self): - super().before_update() - self._set_attr_json("padding", self.__padding) - self._set_attr_json("sizeConstraints", self.__size_constraints) - - # value - @property - def value(self) -> OptionalNumber: - return self._get_attr("value", data_type="float") - - @value.setter - def value(self, value: OptionalNumber): - self._set_attr("value", value) - - # semantics_label - @property - def semantics_label(self) -> Optional[str]: - return self._get_attr("semanticsLabel") - - @semantics_label.setter - def semantics_label(self, value: Optional[str]): - self._set_attr("semanticsLabel", value) - - # track_gap - @property - def track_gap(self) -> OptionalNumber: - return self._get_attr("trackGap", data_type="float") - - @track_gap.setter - def track_gap(self, value: OptionalNumber): - self._set_attr("trackGap", value) - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # size_constraints - @property - def size_constraints(self) -> Optional[BoxConstraints]: - return self.__size_constraints - - @size_constraints.setter - def size_constraints(self, value: Optional[BoxConstraints]): - self.__size_constraints = value - - # year_2023 - @property - def year_2023(self) -> Optional[bool]: - deprecated_property( - name="year_2023", - version="0.27.0", - delete_version=None, # not known for now - reason="Set this flag to False to opt into the 2024 Slider appearance. In the future, this flag will default to False.", - ) - return self._get_attr("year2023", data_type="bool", def_value=True) - - @year_2023.setter - def year_2023(self, value: Optional[bool]): - self._set_attr("year2023", value) - if value is not None: - deprecated_property( - name="year_2023", - version="0.27.0", - delete_version=None, # not known for now - reason="Set this flag to False to opt into the 2024 Slider appearance. In the future, this flag will default to False.", - ) - - # stroke_width - @property - def stroke_width(self) -> float: - return self._get_attr("strokeWidth", data_type="float", def_value=4.0) - - @stroke_width.setter - def stroke_width(self, value: OptionalNumber): - assert value is None or value >= 0, "stroke_width cannot be negative" - self._set_attr("strokeWidth", value) - - # stroke_align - @property - def stroke_align(self) -> float: - return self._get_attr("strokeAlign", data_type="float", def_value=0.0) - - @stroke_align.setter - def stroke_align(self, value: OptionalNumber): - self._set_attr("strokeAlign", value) - - # stroke_cap - @property - def stroke_cap(self) -> Optional[StrokeCap]: - return self.__stroke_cap - - @stroke_cap.setter - def stroke_cap(self, value: Optional[StrokeCap]): - self.__stroke_cap = value - self._set_enum_attr("strokeCap", value, StrokeCap) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) diff --git a/sdk/python/packages/flet/src/flet/core/protocol.py b/sdk/python/packages/flet/src/flet/core/protocol.py deleted file mode 100644 index 6cebebb95..000000000 --- a/sdk/python/packages/flet/src/flet/core/protocol.py +++ /dev/null @@ -1,208 +0,0 @@ -import json -from dataclasses import dataclass, field -from typing import Any, Dict, List, Optional - - -class CommandEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, Message): - return obj.__dict__ - elif isinstance(obj, ClientMessage): - return obj.__dict__ - elif isinstance(obj, Command): - d = {} - if obj.indent > 0: - d["i"] = obj.indent - if obj.name is not None: - d["n"] = obj.name - if obj.values and len(obj.values) > 0: - d["v"] = obj.values - if obj.attrs and len(obj.attrs) > 0: - d["a"] = obj.attrs - if obj.commands and len(obj.commands) > 0: - d["c"] = obj.commands - return d - elif isinstance(obj, object): - return obj.__dict__ - return json.JSONEncoder.default(self, obj) - - -class Actions: - REGISTER_HOST_CLIENT = "registerHostClient" - SESSION_CREATED = "sessionCreated" - PAGE_COMMAND_FROM_HOST = "pageCommandFromHost" - PAGE_COMMANDS_BATCH_FROM_HOST = "pageCommandsBatchFromHost" - PAGE_EVENT_TO_HOST = "pageEventToHost" - - -@dataclass -class Command: - indent: int - name: Optional[str] - values: List[str] = field(default_factory=list) - attrs: Dict[str, str] = field(default_factory=dict) - commands: List[Any] = field(default_factory=list) - - def __str__(self): - return f"{self.name} {self.values} {self.attrs}" - - -@dataclass -class Message: - id: str - action: str - payload: Any - - -@dataclass -class PageCommandRequestPayload: - pageName: str - sessionID: str - command: Command - - -@dataclass -class PageCommandResponsePayload: - result: str - error: str - - -@dataclass -class PageCommandsBatchRequestPayload: - pageName: str - sessionID: str - commands: List[Command] - - -@dataclass -class PageCommandsBatchResponsePayload: - results: List[str] - error: str - - -@dataclass -class PageEventPayload: - pageName: str - sessionID: str - eventTarget: str - eventName: str - eventData: str - - -@dataclass -class RegisterHostClientRequestPayload: - hostClientID: Optional[str] - pageName: str - assetsDir: Optional[str] - authToken: Optional[str] - permissions: Optional[str] - - -@dataclass -class RegisterHostClientResponsePayload: - hostClientID: str - pageName: str - sessionID: str - error: str - - -@dataclass -class PageSessionCreatedPayload: - pageName: str - sessionID: str - - -# -# Local client protocol for desktop apps -# - - -class ClientActions: - REGISTER_WEB_CLIENT = "registerWebClient" - PAGE_EVENT_FROM_WEB = "pageEventFromWeb" - SESSION_CRASHED = "sessionCrashed" - INVOKE_METHOD = "invokeMethod" - PAGE_CONTROLS_BATCH = "pageControlsBatch" - ADD_PAGE_CONTROLS = "addPageControls" - UPDATE_CONTROL_PROPS = "updateControlProps" - CLEAN_CONTROL = "cleanControl" - REMOVE_CONTROL = "removeControl" - - -@dataclass -class ClientMessage: - action: str - payload: Any - - -@dataclass -class RegisterWebClientRequestPayload: - pageName: str - pageRoute: str - pageWidth: str - pageHeight: str - windowWidth: str - windowHeight: str - windowTop: str - windowLeft: str - isPWA: str - isWeb: str - isDebug: str - platform: str - platformBrightness: str - media: str - sessionId: str - - -@dataclass -class SessionPayload: - id: str - controls: Dict[str, Dict[str, Any]] - - -@dataclass -class RegisterWebClientResponsePayload: - session: SessionPayload - error: str - appInactive: bool - - -@dataclass -class PageEventFromWebPayload: - eventTarget: str - eventName: str - eventData: str - - -@dataclass -class SessionCrashedPayload: - message: str - - -@dataclass -class InvokeMethodPayload: - methodId: str - methodName: str - controlId: str - arguments: Dict[str, str] - - -@dataclass -class AddPageControlsPayload: - controls: List[Dict[str, Any]] - trimIDs: List[str] = field(default_factory=lambda: []) - - -@dataclass -class UpdateControlPropsPayload: - props: List[Dict[str, str]] - - -@dataclass -class CleanControlPayload: - ids: List[str] - - -@dataclass -class RemoveControlPayload: - ids: List[str] diff --git a/sdk/python/packages/flet/src/flet/core/radio.py b/sdk/python/packages/flet/src/flet/core/radio.py deleted file mode 100644 index 04884f9b5..000000000 --- a/sdk/python/packages/flet/src/flet/core/radio.py +++ /dev/null @@ -1,310 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - ControlStateValue, - LabelPosition, - MouseCursor, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, - VisualDensity, -) - -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal - - -class Radio(ConstrainedControl, AdaptiveControl): - """ - Radio buttons let people select a single option from two or more choices. - - Example: - ``` - import flet as ft - - def main(page): - def button_clicked(e): - t.value = f"Your favorite color is: {cg.value}" - page.update() - - t = ft.Text() - b = ft.ElevatedButton(text='Submit', on_click=button_clicked) - cg = ft.RadioGroup(content=ft.Column([ - ft.Radio(value="red", label="Red"), - ft.Radio(value="green", label="Green"), - ft.Radio(value="blue", label="Blue")])) - - page.add(ft.Text("Select your favorite color:"), cg, b, t) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/radio - """ - - def __init__( - self, - label: Optional[str] = None, - label_position: Optional[LabelPosition] = None, - label_style: Optional[TextStyle] = None, - value: Optional[str] = None, - autofocus: Optional[bool] = None, - fill_color: ControlStateValue[ColorValue] = None, - active_color: Optional[ColorValue] = None, - overlay_color: ControlStateValue[ColorValue] = None, - hover_color: Optional[ColorValue] = None, - focus_color: Optional[ColorValue] = None, - splash_radius: OptionalNumber = None, - toggleable: Optional[bool] = None, - visual_density: Optional[VisualDensity] = None, - mouse_cursor: Optional[MouseCursor] = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl and AdaptiveControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.value = value - self.label = label - self.label_style = label_style - self.label_position = label_position - self.autofocus = autofocus - self.fill_color = fill_color - self.active_color = active_color - self.on_focus = on_focus - self.on_blur = on_blur - self.overlay_color = overlay_color - self.hover_color = hover_color - self.focus_color = focus_color - self.splash_radius = splash_radius - self.toggleable = toggleable - self.visual_density = visual_density - self.mouse_cursor = mouse_cursor - - def _get_control_name(self): - return "radio" - - def before_update(self): - super().before_update() - self._set_attr_json("fillColor", self.__fill_color, wrap_attr_dict=True) - self._set_attr_json("overlayColor", self.__overlay_color, wrap_attr_dict=True) - self._set_attr_json("labelStyle", self.__label_style) - - # value - @property - def value(self) -> Optional[str]: - return self._get_attr("value", def_value="") - - @value.setter - def value(self, value: Optional[str]): - self._set_attr("value", value) - - # active_color - @property - def active_color(self) -> Optional[ColorValue]: - return self.__active_color - - @active_color.setter - def active_color(self, value: Optional[ColorValue]): - self.__active_color = value - self._set_enum_attr("activeColor", value, ColorEnums) - - # focus_color - @property - def focus_color(self) -> Optional[ColorValue]: - return self.__focus_color - - @focus_color.setter - def focus_color(self, value: Optional[ColorValue]): - self.__focus_color = value - self._set_enum_attr("focusColor", value, ColorEnums) - - # splash_radius - @property - def splash_radius(self) -> OptionalNumber: - return self._get_attr("splashRadius", data_type="float") - - @splash_radius.setter - def splash_radius(self, value: OptionalNumber): - self._set_attr("splashRadius", value) - - # toggleable - @property - def toggleable(self) -> bool: - return self._get_attr("toggleable", data_type="bool", def_value=False) - - @toggleable.setter - def toggleable(self, value: Optional[bool]): - self._set_attr("toggleable", value) - - # visual_density - @property - def visual_density(self) -> Optional[VisualDensity]: - return self.__visual_density - - @visual_density.setter - def visual_density(self, value: Optional[VisualDensity]): - self.__visual_density = value - self._set_enum_attr("visualDensity", value, VisualDensity) - - # label - @property - def label(self) -> Optional[str]: - return self._get_attr("label") - - @label.setter - def label(self, value: Optional[str]): - self._set_attr("label", value) - - # label_position - @property - def label_position(self) -> Optional[LabelPosition]: - return self.__label_position - - @label_position.setter - def label_position(self, value: Optional[LabelPosition]): - self.__label_position = value - self._set_enum_attr("labelPosition", value, LabelPosition) - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self.__mouse_cursor - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self.__mouse_cursor = value - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # label_style - @property - def label_style(self) -> Optional[TextStyle]: - return self.__label_style - - @label_style.setter - def label_style(self, value: Optional[TextStyle]): - self.__label_style = value - - # fill_color - @property - def fill_color(self) -> ControlStateValue[str]: - return self.__fill_color - - @fill_color.setter - def fill_color(self, value: ControlStateValue[str]): - self.__fill_color = value - - # overlay_color - @property - def overlay_color(self) -> ControlStateValue[str]: - return self.__overlay_color - - @overlay_color.setter - def overlay_color(self, value: ControlStateValue[str]): - self.__overlay_color = value - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) diff --git a/sdk/python/packages/flet/src/flet/core/radio_group.py b/sdk/python/packages/flet/src/flet/core/radio_group.py deleted file mode 100644 index f095ec411..000000000 --- a/sdk/python/packages/flet/src/flet/core/radio_group.py +++ /dev/null @@ -1,102 +0,0 @@ -from typing import Any, Optional - -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.types import OptionalControlEventCallable - - -class RadioGroup(Control): - """ - Radio buttons let people select a single option from two or more choices. - - Example: - ``` - import flet as ft - - def main(page): - def button_clicked(e): - t.value = f"Your favorite color is: {cg.value}" - page.update() - - t = ft.Text() - b = ft.ElevatedButton(text='Submit', on_click=button_clicked) - cg = ft.RadioGroup(content=ft.Column([ - ft.Radio(value="red", label="Red"), - ft.Radio(value="green", label="Green"), - ft.Radio(value="blue", label="Blue")])) - - page.add(ft.Text("Select your favorite color:"), cg, b, t) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/radio - """ - - def __init__( - self, - content: Control, - value: Optional[str] = None, - on_change: OptionalControlEventCallable = None, - # - # Control - # - ref: Optional[Ref] = None, - opacity: OptionalNumber = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - opacity=opacity, - visible=visible, - disabled=disabled, - data=data, - ) - - self.content = content - self.value = value - self.on_change = on_change - - def _get_control_name(self): - return "radiogroup" - - def _get_children(self): - self.__content._set_attr_internal("n", "content") - return [self.__content] - - def before_update(self): - super().before_update() - assert self.__content.visible, "content must be visible" - - # value - @property - def value(self) -> Optional[str]: - return self._get_attr("value") - - @value.setter - def value(self, value: Optional[str]): - self._set_attr("value", value) - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) diff --git a/sdk/python/packages/flet/src/flet/core/range_slider.py b/sdk/python/packages/flet/src/flet/core/range_slider.py deleted file mode 100644 index ad303ffc6..000000000 --- a/sdk/python/packages/flet/src/flet/core/range_slider.py +++ /dev/null @@ -1,317 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - ControlStateValue, - MouseCursor, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class RangeSlider(ConstrainedControl): - """ - A Material Design range slider. Used to select a range from a range of values. - A range slider can be used to select from either a continuous or a discrete set of values. - The default is to use a continuous range of values from min to max. - - Example: - ``` - import flet as ft - - - def range_slider_changed(e): - print(f"On change! Values are ({e.control.start_value}, {e.control.end_value})") - - - def range_slider_started_change(e): - print( - f"On change start! Values are ({e.control.start_value}, {e.control.end_value})" - ) - - - def range_slider_ended_change(e): - print(f"On change end! Values are ({e.control.start_value}, {e.control.end_value})") - - - def main(page: ft.Page): - range_slider = ft.RangeSlider( - min=0, - max=50, - start_value=10, - divisions=10, - end_value=20, - inactive_color=ft.colors.GREEN_300, - active_color=ft.colors.GREEN_700, - overlay_color=ft.colors.GREEN_100, - on_change=range_slider_changed, - on_change_start=range_slider_started_change, - on_change_end=range_slider_ended_change, - label="{value}%", - ) - - page.add( - ft.Column( - horizontal_alignment=ft.CrossAxisAlignment.CENTER, - controls=[ - ft.Text("Range slider", size=20, weight=ft.FontWeight.BOLD), - range_slider, - ], - ) - ) - - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/rangeslider - """ - - def __init__( - self, - start_value: float, - end_value: float, - label: Optional[str] = None, - min: OptionalNumber = None, - max: OptionalNumber = None, - divisions: Optional[int] = None, - round: Optional[int] = None, - active_color: Optional[ColorValue] = None, - inactive_color: Optional[ColorValue] = None, - overlay_color: ControlStateValue[ColorValue] = None, - mouse_cursor: ControlStateValue[MouseCursor] = None, - on_change: OptionalControlEventCallable = None, - on_change_start: OptionalControlEventCallable = None, - on_change_end: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - self.start_value = start_value - self.end_value = end_value - self.label = label - - self.min = min - self.max = max - self.divisions = divisions - self.round = round - self.active_color = active_color - self.inactive_color = inactive_color - self.overlay_color = overlay_color - self.mouse_cursor = mouse_cursor - self.on_change = on_change - self.on_change_start = on_change_start - self.on_change_end = on_change_end - - def _get_control_name(self): - return "rangeslider" - - def before_update(self): - super().before_update() - self._set_attr_json("overlayColor", self.__overlay_color, wrap_attr_dict=True) - self._set_attr_json("mouseCursor", self.__mouse_cursor, wrap_attr_dict=True) - - # start_value - @property - def start_value(self) -> float: - return self._get_attr("startValue", data_type="float") - - @start_value.setter - def start_value(self, value: float): - self._set_attr("startValue", value) - - # end_value - @property - def end_value(self) -> float: - return self._get_attr("endValue", data_type="float") - - @end_value.setter - def end_value(self, value: float): - self._set_attr("endValue", value) - - # label - @property - def label(self) -> Optional[str]: - return self._get_attr("label") - - @label.setter - def label(self, value: Optional[str]): - self._set_attr("label", value) - - # min - @property - def min(self) -> OptionalNumber: - return self._get_attr("min", data_type="float") - - @min.setter - def min(self, value: OptionalNumber): - if value is not None: - if self.max is not None: - assert value <= self.max, "min must be less than or equal to max" - self._set_attr("min", value) - - # max - @property - def max(self) -> OptionalNumber: - return self._get_attr("max", data_type="float") - - @max.setter - def max(self, value: OptionalNumber): - if value is not None: - if self.min is not None: - assert value >= self.min, "max must be greater than or equal to min" - self._set_attr("max", value) - - # divisions - @property - def divisions(self) -> Optional[int]: - return self._get_attr("divisions") - - @divisions.setter - def divisions(self, value: Optional[int]): - self._set_attr("divisions", value) - - # round - @property - def round(self) -> Optional[int]: - return self._get_attr("round") - - @round.setter - def round(self, value: Optional[int]): - self._set_attr("round", value) - - # active_color - @property - def active_color(self) -> Optional[ColorValue]: - return self.__active_color - - @active_color.setter - def active_color(self, value: Optional[ColorValue]): - self.__active_color = value - self._set_enum_attr("activeColor", value, ColorEnums) - - # inactive_color - @property - def inactive_color(self) -> Optional[ColorValue]: - return self.__inactive_color - - @inactive_color.setter - def inactive_color(self, value: Optional[ColorValue]): - self.__inactive_color = value - self._set_enum_attr("inactiveColor", value, ColorEnums) - - # overlay_color - @property - def overlay_color(self) -> ControlStateValue[ColorValue]: - return self.__overlay_color - - @overlay_color.setter - def overlay_color(self, value: ControlStateValue[ColorValue]): - self.__overlay_color = value - - # mouse_cursor - @property - def mouse_cursor(self) -> ControlStateValue[MouseCursor]: - return self.__mouse_cursor - - @mouse_cursor.setter - def mouse_cursor(self, value: ControlStateValue[MouseCursor]): - self.__mouse_cursor = value - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - - # on_change_start - @property - def on_change_start(self) -> OptionalControlEventCallable: - return self._get_event_handler("change_start") - - @on_change_start.setter - def on_change_start(self, handler: OptionalControlEventCallable): - self._add_event_handler("change_start", handler) - - # on_change_end - @property - def on_change_end(self) -> OptionalControlEventCallable: - return self._get_event_handler("change_end") - - @on_change_end.setter - def on_change_end(self, handler: OptionalControlEventCallable): - self._add_event_handler("change_end", handler) diff --git a/sdk/python/packages/flet/src/flet/core/ref.py b/sdk/python/packages/flet/src/flet/core/ref.py deleted file mode 100644 index 33df0977e..000000000 --- a/sdk/python/packages/flet/src/flet/core/ref.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Generic, TypeVar - -T = TypeVar("T") - - -class Ref(Generic[T]): - def __init__(self): - self._current: T = None - - @property - def current(self) -> T: - return self._current - - @current.setter - def current(self, value: T): - self._current = value diff --git a/sdk/python/packages/flet/src/flet/core/reorderable_draggable.py b/sdk/python/packages/flet/src/flet/core/reorderable_draggable.py deleted file mode 100644 index c32c67a8e..000000000 --- a/sdk/python/packages/flet/src/flet/core/reorderable_draggable.py +++ /dev/null @@ -1,117 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class ReorderableDraggable(ConstrainedControl, AdaptiveControl): - def __init__( - self, - index: int, - content: Control, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - # - # Adaptive - # - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - visible=visible, - disabled=disabled, - data=data, - rtl=rtl, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.content = content - self.index = index - - def _get_control_name(self): - return "reorderabledraggable" - - def before_update(self): - super().before_update() - assert self.__content.visible, "content must be visible" - - def _get_children(self): - self.__content._set_attr_internal("n", "content") - return [self.__content] - - # index - @property - def index(self) -> int: - return self._get_attr("index", data_type="int") - - @index.setter - def index(self, value: int): - self._set_attr("index", value) - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value diff --git a/sdk/python/packages/flet/src/flet/core/reorderable_list_view.py b/sdk/python/packages/flet/src/flet/core/reorderable_list_view.py deleted file mode 100644 index aa65a4b82..000000000 --- a/sdk/python/packages/flet/src/flet/core/reorderable_list_view.py +++ /dev/null @@ -1,266 +0,0 @@ -import json -from typing import Any, Optional, Sequence, Union - -from flet.core.animation import AnimationValue -from flet.core.control import Control, OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.list_view import ListView -from flet.core.ref import Ref -from flet.core.scrollable_control import OnScrollEvent -from flet.core.types import ( - ClipBehavior, - MouseCursor, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, - MouseCursor, -) - - -class OnReorderEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.new_index: Optional[int] = d.get("new") - self.old_index: Optional[int] = d.get("old") - - -class ReorderableListView(ListView): - """ - A scrollable list of controls that can be reordered. - - ----- - - Online docs: https://flet.dev/docs/controls/reorderablelistview - """ - - def __init__( - self, - controls: Optional[Sequence[Control]] = None, - horizontal: Optional[bool] = None, - item_extent: OptionalNumber = None, - first_item_prototype: Optional[bool] = None, - padding: PaddingValue = None, - clip_behavior: Optional[ClipBehavior] = None, - cache_extent: OptionalNumber = None, - anchor: OptionalNumber = None, - auto_scroller_velocity_scalar: OptionalNumber = None, - header: Optional[Control] = None, - footer: Optional[Control] = None, - build_controls_on_demand: Optional[bool] = None, - show_default_drag_handles: Optional[bool] = None, - mouse_cursor: Optional[MouseCursor] = None, - on_reorder: OptionalEventCallable[OnReorderEvent] = None, - on_reorder_start: OptionalEventCallable[OnReorderEvent] = None, - on_reorder_end: OptionalEventCallable[OnReorderEvent] = None, - # - # ScrollableControl - # - auto_scroll: Optional[bool] = None, - reverse: Optional[bool] = None, - on_scroll_interval: OptionalNumber = None, - on_scroll: OptionalEventCallable[OnScrollEvent] = None, - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: RotateValue = None, - scale: ScaleValue = None, - offset: OffsetValue = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = None, - ): - ListView.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - auto_scroll=auto_scroll, - reverse=reverse, - on_scroll_interval=on_scroll_interval, - on_scroll=on_scroll, - adaptive=adaptive, - controls=controls, - horizontal=horizontal, - item_extent=item_extent, - first_item_prototype=first_item_prototype, - padding=padding, - clip_behavior=clip_behavior, - cache_extent=cache_extent, - build_controls_on_demand=build_controls_on_demand, - ) - self.__on_reorder = EventHandler(lambda e: OnReorderEvent(e)) - self._add_event_handler("reorder", self.__on_reorder.get_handler()) - - self.__on_reorder_start = EventHandler(lambda e: OnReorderEvent(e)) - self._add_event_handler("reorder_start", self.__on_reorder_start.get_handler()) - - self.__on_reorder_end = EventHandler(lambda e: OnReorderEvent(e)) - self._add_event_handler("reorder_end", self.__on_reorder_end.get_handler()) - - self.header = header - self.footer = footer - self.on_reorder = on_reorder - self.anchor = anchor - self.auto_scroller_velocity_scalar = auto_scroller_velocity_scalar - self.show_default_drag_handles = show_default_drag_handles - self.mouse_cursor = mouse_cursor - self.on_reorder_start = on_reorder_start - self.on_reorder_end = on_reorder_end - self.mouse_cursor = mouse_cursor - - def _get_control_name(self): - return "reorderablelistview" - - def before_update(self): - super().before_update() - - def _get_children(self): - children = super()._get_children() - if self.header: - self.__header._set_attr_internal("n", "header") - children.append(self.header) - if self.footer: - self.__footer._set_attr_internal("n", "footer") - children.append(self.footer) - return children - - # anchor - @property - def anchor(self) -> OptionalNumber: - return self._get_attr("anchor", data_type="float", def_value=0.0) - - @anchor.setter - def anchor(self, value: OptionalNumber): - self._set_attr("anchor", value) - - # auto_scroller_velocity_scalar - @property - def auto_scroller_velocity_scalar(self) -> OptionalNumber: - return self._get_attr("autoScrollerVelocityScalar", data_type="float") - - @auto_scroller_velocity_scalar.setter - def auto_scroller_velocity_scalar(self, value: OptionalNumber): - self._set_attr("autoScrollerVelocityScalar", value) - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self.__mouse_cursor - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self.__mouse_cursor = value - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # header - @property - def header(self) -> Optional[Control]: - return self.__header - - @header.setter - def header(self, value: Optional[Control]): - self.__header = value - - # footer - @property - def footer(self) -> Optional[Control]: - return self.__footer - - @footer.setter - def footer(self, value: Optional[Control]): - self.__footer = value - - # show_default_drag_handles - @property - def show_default_drag_handles(self) -> Optional[bool]: - return self._get_attr( - "showDefaultDragHandles", data_type="bool", def_value=True - ) - - @show_default_drag_handles.setter - def show_default_drag_handles(self, value: Optional[bool]): - self._set_attr("showDefaultDragHandles", value) - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self.__mouse_cursor - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self.__mouse_cursor = value - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # on_reorder - @property - def on_reorder(self) -> OptionalEventCallable[OnReorderEvent]: - return self.__on_reorder.handler - - @on_reorder.setter - def on_reorder(self, handler: OptionalEventCallable[OnReorderEvent]): - self.__on_reorder.handler = handler - - # on_reorder_start - @property - def on_reorder_start(self) -> OptionalEventCallable[OnReorderEvent]: - return self.__on_reorder_start.handler - - @on_reorder_start.setter - def on_reorder_start(self, handler: OptionalEventCallable[OnReorderEvent]): - self.__on_reorder_start.handler = handler - - # on_reorder_end - @property - def on_reorder_end(self) -> OptionalEventCallable[OnReorderEvent]: - return self.__on_reorder_end.handler - - @on_reorder_end.setter - def on_reorder_end(self, handler: OptionalEventCallable[OnReorderEvent]): - self.__on_reorder_end.handler = handler diff --git a/sdk/python/packages/flet/src/flet/core/responsive_row.py b/sdk/python/packages/flet/src/flet/core/responsive_row.py deleted file mode 100644 index 9eb5dd5a6..000000000 --- a/sdk/python/packages/flet/src/flet/core/responsive_row.py +++ /dev/null @@ -1,204 +0,0 @@ -from typing import Any, Optional, Sequence, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.types import ( - CrossAxisAlignment, - MainAxisAlignment, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class ResponsiveRow(ConstrainedControl, AdaptiveControl): - """ - ResponsiveRow allows aligning child controls to virtual columns. By default, a virtual grid has 12 columns, but that can be customized with `ResponsiveRow.columns` property. - - Similar to `expand` property, every control now has `col` property which allows specifying how many columns a control should span. - - Example: - - ``` - import flet as ft - - def main(page: ft.Page): - - page.add( - ft.ResponsiveRow( - [ - ft.TextField(label="TextField 1", col={"md": 4}), - ft.TextField(label="TextField 2", col={"md": 4}), - ft.TextField(label="TextField 3", col={"md": 4}), - ], - run_spacing={"xs": 10}, - ), - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/responsiverow - """ - - def __init__( - self, - controls: Optional[Sequence[Control]] = None, - columns: Optional[ResponsiveNumber] = None, - alignment: Optional[MainAxisAlignment] = None, - vertical_alignment: Optional[CrossAxisAlignment] = None, - spacing: Optional[ResponsiveNumber] = None, - run_spacing: Optional[ResponsiveNumber] = None, - rtl: Optional[bool] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - # - # AdaptiveControl - # - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - rtl=rtl, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.controls = controls - self.alignment = alignment - self.vertical_alignment = vertical_alignment - self.spacing = spacing - self.run_spacing = run_spacing - self.columns = columns - - def _get_control_name(self): - return "responsiverow" - - def before_update(self): - super().before_update() - self._set_attr_json("columns", self.__columns) - self._set_attr_json("spacing", self.__spacing) - self._set_attr_json("runSpacing", self.__run_spacing) - - def _get_children(self): - return self.__controls - - def clean(self): - super().clean() - self.__controls.clear() - - # horizontal_alignment - @property - def alignment(self) -> Optional[MainAxisAlignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[MainAxisAlignment]): - self.__alignment = value - self._set_enum_attr("alignment", value, MainAxisAlignment) - - # vertical_alignment - @property - def vertical_alignment(self) -> Optional[CrossAxisAlignment]: - return self.__vertical_alignment - - @vertical_alignment.setter - def vertical_alignment(self, value: Optional[CrossAxisAlignment]): - self.__vertical_alignment = value - self._set_enum_attr("verticalAlignment", value, CrossAxisAlignment) - - # columns - @property - def columns(self) -> Optional[ResponsiveNumber]: - return self.__columns - - @columns.setter - def columns(self, value: Optional[ResponsiveNumber]): - self.__columns = value - - # spacing - @property - def spacing(self) -> Optional[ResponsiveNumber]: - return self.__spacing - - @spacing.setter - def spacing(self, value: Optional[ResponsiveNumber]): - self.__spacing = value - - # run_spacing - @property - def run_spacing(self) -> Optional[ResponsiveNumber]: - return self.__run_spacing - - @run_spacing.setter - def run_spacing(self, value: Optional[ResponsiveNumber]): - self.__run_spacing = value - - # controls - @property - def controls(self): - return self.__controls - - @controls.setter - def controls(self, value: Optional[Sequence[Control]]): - self.__controls = list(value) if value is not None else [] diff --git a/sdk/python/packages/flet/src/flet/core/rive.py b/sdk/python/packages/flet/src/flet/core/rive.py deleted file mode 100644 index 555e3192c..000000000 --- a/sdk/python/packages/flet/src/flet/core/rive.py +++ /dev/null @@ -1,194 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.alignment import Alignment -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ImageFit, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) -from flet.utils import deprecated - - -@deprecated( - reason="Rive control has been moved to a separate Python package: https://pypi.org/project/flet-rive. " - + "Read more about this change in Flet blog: https://flet.dev/blog/flet-v-0-26-release-announcement", - version="0.26.0", - delete_version="0.29.0", -) -class Rive(ConstrainedControl): - """ - Displays rive animations. - - ----- - - Online docs: https://flet.dev/docs/controls/rive - """ - - def __init__( - self, - src: str, - placeholder: Optional[Control] = None, - artboard: Optional[str] = None, - alignment: Optional[Alignment] = None, - enable_antialiasing: Optional[bool] = None, - use_artboard_size: Optional[bool] = None, - fit: Optional[ImageFit] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - rtl=rtl, - ) - - self.src = src - self.placeholder = placeholder - self.artboard = artboard - self.enable_antialiasing = enable_antialiasing - self.use_artboard_size = use_artboard_size - self.alignment = alignment - self.fit = fit - - def _get_control_name(self): - return "rive" - - def before_update(self): - super().before_update() - assert self.src, "src must be provided" - self._set_attr_json("alignment", self.__alignment) - - def _get_children(self): - if self.__placeholder: - return [self.__placeholder] - return [] - - # src - @property - def src(self) -> Optional[str]: - return self._get_attr("src") - - @src.setter - def src(self, value: Optional[str]): - self._set_attr("src", value) - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value - - # artboard - @property - def artboard(self): - return self._get_attr("artBoard") - - @artboard.setter - def artboard(self, value): - self._set_attr("artBoard", value) - - # enable_antialiasing - @property - def enable_antialiasing(self) -> bool: - return self._get_attr("enableAntiAliasing", def_value=True, data_type="bool") - - @enable_antialiasing.setter - def enable_antialiasing(self, value: Optional[bool]): - self._set_attr("enableAntiAliasing", value) - - # placeholder - @property - def placeholder(self) -> Optional[Control]: - return self.__placeholder - - @placeholder.setter - def placeholder(self, value: Optional[Control]): - self.__placeholder = value - - # use_artboard_size - @property - def use_artboard_size(self) -> bool: - return self._get_attr("useArtBoardSize", def_value=False, data_type="bool") - - @use_artboard_size.setter - def use_artboard_size(self, value: Optional[bool]): - self._set_attr("useArtBoardSize", value) - - # fit - @property - def fit(self) -> Optional[ImageFit]: - return self.__fit - - @fit.setter - def fit(self, value: Optional[ImageFit]): - self.__fit = value - self._set_enum_attr("fit", value, ImageFit) diff --git a/sdk/python/packages/flet/src/flet/core/row.py b/sdk/python/packages/flet/src/flet/core/row.py deleted file mode 100644 index 30553d339..000000000 --- a/sdk/python/packages/flet/src/flet/core/row.py +++ /dev/null @@ -1,251 +0,0 @@ -from typing import Any, List, Optional, Sequence, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control -from flet.core.ref import Ref -from flet.core.scrollable_control import OnScrollEvent, ScrollableControl -from flet.core.types import ( - CrossAxisAlignment, - MainAxisAlignment, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - OptionalNumber, - ResponsiveNumber, - RotateValue, - ScaleValue, - ScrollMode, -) - - -class Row(ConstrainedControl, ScrollableControl, AdaptiveControl): - """ - A control that displays its children in a horizontal array. - - To cause a child control to expand and fill the available horizontal space, set its `expand` property. - - Example: - - ``` - import flet as ft - - - def main(page: ft.Page): - page.title = "Row example" - - page.add( - ft.Row( - controls=[ - ft.Container( - expand=1, - content=ft.Text("Container 1"), - bgcolor=ft.colors.GREEN_100, - ), - ft.Container( - expand=2, content=ft.Text("Container 2"), bgcolor=ft.colors.RED_100 - ), - ], - ), - ), - - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/row - """ - - def __init__( - self, - controls: Optional[Sequence[Control]] = None, - alignment: Optional[MainAxisAlignment] = None, - vertical_alignment: Optional[CrossAxisAlignment] = None, - spacing: OptionalNumber = None, - tight: Optional[bool] = None, - wrap: Optional[bool] = None, - run_spacing: OptionalNumber = None, - run_alignment: Optional[MainAxisAlignment] = None, - # - # ScrollableControl specific - # - scroll: Optional[ScrollMode] = None, - auto_scroll: Optional[bool] = None, - on_scroll_interval: OptionalNumber = None, - on_scroll: OptionalEventCallable[OnScrollEvent] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - # - # Adaptive - # - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - rtl=rtl, - ) - - ScrollableControl.__init__( - self, - scroll=scroll, - auto_scroll=auto_scroll, - on_scroll_interval=on_scroll_interval, - on_scroll=on_scroll, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.controls = controls - self.alignment = alignment - self.vertical_alignment = vertical_alignment - self.spacing = spacing - self.tight = tight - self.wrap = wrap - self.run_spacing = run_spacing - self.run_alignment = run_alignment - - def _get_control_name(self): - return "row" - - def _get_children(self): - return self.__controls - - def __contains__(self, item): - return item in self.__controls - - # Public methods - def clean(self): - super().clean() - self.__controls.clear() - - # tight - @property - def tight(self) -> bool: - return self._get_attr("tight", data_type="bool", def_value=False) - - @tight.setter - def tight(self, value: Optional[bool]): - self._set_attr("tight", value) - - # alignment - @property - def alignment(self) -> Optional[MainAxisAlignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[MainAxisAlignment]): - self.__alignment = value - self._set_enum_attr("alignment", value, MainAxisAlignment) - - # run_alignment - @property - def run_alignment(self) -> Optional[MainAxisAlignment]: - return self.__run_alignment - - @run_alignment.setter - def run_alignment(self, value: Optional[MainAxisAlignment]): - self.__run_alignment = value - self._set_enum_attr("runAlignment", value, MainAxisAlignment) - - # vertical_alignment - @property - def vertical_alignment(self) -> Optional[CrossAxisAlignment]: - return self.__vertical_alignment - - @vertical_alignment.setter - def vertical_alignment(self, value: Optional[CrossAxisAlignment]): - self.__vertical_alignment = value - self._set_enum_attr("verticalAlignment", value, CrossAxisAlignment) - - # spacing - @property - def spacing(self) -> OptionalNumber: - return self._get_attr("spacing", data_type="float", def_value=10) - - @spacing.setter - def spacing(self, value: OptionalNumber): - self._set_attr("spacing", value) - - # wrap - @property - def wrap(self) -> bool: - return self._get_attr("wrap", data_type="bool", def_value=False) - - @wrap.setter - def wrap(self, value: Optional[bool]): - self._set_attr("wrap", value) - - # run_spacing - @property - def run_spacing(self) -> OptionalNumber: - return self._get_attr("runSpacing", data_type="float") - - @run_spacing.setter - def run_spacing(self, value: OptionalNumber): - self._set_attr("runSpacing", value) - - # controls - @property - def controls(self) -> List[Control]: - return self.__controls - - @controls.setter - def controls(self, value: Optional[Sequence[Control]]): - self.__controls = list(value) if value else [] diff --git a/sdk/python/packages/flet/src/flet/core/safe_area.py b/sdk/python/packages/flet/src/flet/core/safe_area.py deleted file mode 100644 index 35af8115d..000000000 --- a/sdk/python/packages/flet/src/flet/core/safe_area.py +++ /dev/null @@ -1,177 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class SafeArea(ConstrainedControl, AdaptiveControl): - def __init__( - self, - content: Control, - left: Optional[bool] = None, - top: Optional[bool] = None, - right: Optional[bool] = None, - bottom: Optional[bool] = None, - maintain_bottom_view_padding: Optional[bool] = None, - minimum_padding: Optional[PaddingValue] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - # - # Adaptive - # - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - rtl=rtl, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.content = content - self.left = left - self.top = top - self.right = right - self.bottom = bottom - self.maintain_bottom_view_padding = maintain_bottom_view_padding - self.minimum_padding = minimum_padding - - def _get_control_name(self): - return "safearea" - - def before_update(self): - super().before_update() - assert self.__content.visible, "content must be visible" - self._set_attr_json("minimumPadding", self.__minimum_padding) - - def _get_children(self): - self.__content._set_attr_internal("n", "content") - return [self.__content] - - # left - @property - def left(self) -> bool: - return self._get_attr("left", data_type="bool", def_value=True) - - @left.setter - def left(self, value: Optional[bool]): - self._set_attr("left", value) - - # top - @property - def top(self) -> bool: - return self._get_attr("top", data_type="bool", def_value=True) - - @top.setter - def top(self, value: Optional[bool]): - self._set_attr("top", value) - - # right - @property - def right(self) -> bool: - return self._get_attr("right", data_type="bool", def_value=True) - - @right.setter - def right(self, value: Optional[bool]): - self._set_attr("right", value) - - # bottom - @property - def bottom(self) -> bool: - return self._get_attr("bottom", data_type="bool", def_value=True) - - @bottom.setter - def bottom(self, value: Optional[bool]): - self._set_attr("bottom", value) - - # maintain_bottom_view_padding - @property - def maintain_bottom_view_padding(self) -> bool: - return self._get_attr( - "maintainBottomViewPadding", data_type="bool", def_value=False - ) - - @maintain_bottom_view_padding.setter - def maintain_bottom_view_padding(self, value: Optional[bool]): - self._set_attr("maintainBottomViewPadding", value) - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # minimum_padding - @property - def minimum_padding(self) -> Optional[PaddingValue]: - return self.__minimum_padding - - @minimum_padding.setter - def minimum_padding(self, value: Optional[PaddingValue]): - self.__minimum_padding = value diff --git a/sdk/python/packages/flet/src/flet/core/scrollable_control.py b/sdk/python/packages/flet/src/flet/core/scrollable_control.py deleted file mode 100644 index 8151c4626..000000000 --- a/sdk/python/packages/flet/src/flet/core/scrollable_control.py +++ /dev/null @@ -1,122 +0,0 @@ -import json -import time -from typing import Optional - -from flet.core.animation import AnimationCurve -from flet.core.control import Control, OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.types import OptionalEventCallable, ScrollMode - - -class ScrollableControl(Control): - def __init__( - self, - scroll: Optional[ScrollMode] = None, - auto_scroll: Optional[bool] = None, - reverse: Optional[bool] = None, - on_scroll_interval: OptionalNumber = None, - on_scroll: OptionalEventCallable["OnScrollEvent"] = None, - ): - super().__init__() - self.__on_scroll = EventHandler(lambda e: OnScrollEvent(e)) - self._add_event_handler("onScroll", self.__on_scroll.get_handler()) - - self.scroll = scroll - self.auto_scroll = auto_scroll - self.reverse = reverse - self.on_scroll_interval = on_scroll_interval - self.on_scroll = on_scroll - - def scroll_to( - self, - offset: Optional[float] = None, - delta: Optional[float] = None, - key: Optional[str] = None, - duration: Optional[int] = None, - curve: Optional[AnimationCurve] = None, - ): - m = { - "n": "scroll_to", - "i": str(time.time()), - "p": { - "offset": offset, - "delta": delta, - "key": key, - "duration": duration, - "curve": curve.value if curve is not None else None, - }, - } - self._set_attr_json("method", m) - self.update() - - # scroll - @property - def scroll(self) -> Optional[ScrollMode]: - return self.__scroll - - @scroll.setter - def scroll(self, value: Optional[ScrollMode]): - self.__scroll = value - self._set_attr( - "scroll", - value.value - if isinstance(value, ScrollMode) - else "auto" - if value is True - else None - if value is False - else value, - ) - - # auto_scroll - @property - def auto_scroll(self) -> bool: - return self._get_attr("autoScroll", data_type="bool", def_value=False) - - @auto_scroll.setter - def auto_scroll(self, value: Optional[bool]): - self._set_attr("autoScroll", value) - - # reverse - @property - def reverse(self) -> bool: - return self._get_attr("reverse", data_type="bool", def_value=False) - - @reverse.setter - def reverse(self, value: Optional[bool]): - self._set_attr("reverse", value) - - # on_scroll_interval - @property - def on_scroll_interval(self) -> OptionalNumber: - return self._get_attr("onScrollInterval") - - @on_scroll_interval.setter - def on_scroll_interval(self, value: OptionalNumber): - self._set_attr("onScrollInterval", value) - - # on_scroll - @property - def on_scroll(self) -> OptionalEventCallable["OnScrollEvent"]: - return self.__on_scroll.handler - - @on_scroll.setter - def on_scroll(self, handler: OptionalEventCallable["OnScrollEvent"]): - self.__on_scroll.handler = handler - self._set_attr("onScroll", True if handler is not None else None) - - -class OnScrollEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.event_type: str = d.get("t") - self.pixels: float = d.get("p") - self.min_scroll_extent: float = d.get("minse") - self.max_scroll_extent: float = d.get("maxse") - self.viewport_dimension: float = d.get("vd") - self.scroll_delta: Optional[float] = d.get("sd") - self.direction: Optional[str] = d.get("dir") - self.overscroll: Optional[float] = d.get("os") - self.velocity: Optional[float] = d.get("v") diff --git a/sdk/python/packages/flet/src/flet/core/search_bar.py b/sdk/python/packages/flet/src/flet/core/search_bar.py deleted file mode 100644 index 4026a0922..000000000 --- a/sdk/python/packages/flet/src/flet/core/search_bar.py +++ /dev/null @@ -1,623 +0,0 @@ -import time -from typing import Any, Dict, List, Optional, Sequence, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.border import BorderSide -from flet.core.box import BoxConstraints -from flet.core.buttons import OutlinedBorder -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.textfield import KeyboardType, TextCapitalization -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - ControlState, - ControlStateValue, - Number, - OffsetValue, - OptionalControlEventCallable, - OptionalNumber, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class SearchBar(ConstrainedControl): - """ - Manages a "search view" route that allows the user to select one of the suggested completions for a search query. - - ----- - - Online docs: https://flet.dev/docs/controls/searchbar - """ - - def __init__( - self, - controls: Optional[Sequence[Control]] = None, - value: Optional[str] = None, - bar_leading: Optional[Control] = None, - bar_trailing: Optional[List[Control]] = None, - bar_hint_text: Optional[str] = None, - bar_bgcolor: ControlStateValue[ColorValue] = None, - bar_overlay_color: ControlStateValue[ColorValue] = None, - bar_shadow_color: ControlStateValue[ColorValue] = None, - bar_surface_tint_color: ControlStateValue[ColorValue] = None, - bar_elevation: ControlStateValue[OptionalNumber] = None, - bar_border_side: ControlStateValue[BorderSide] = None, - bar_shape: ControlStateValue[OutlinedBorder] = None, - bar_text_style: ControlStateValue[TextStyle] = None, - bar_hint_text_style: ControlStateValue[TextStyle] = None, - bar_padding: ControlStateValue[PaddingValue] = None, - bar_scroll_padding: Optional[PaddingValue] = None, - view_leading: Optional[Control] = None, - view_trailing: Optional[List[Control]] = None, - view_elevation: OptionalNumber = None, - view_bgcolor: Optional[ColorValue] = None, - view_hint_text: Optional[str] = None, - view_side: Optional[BorderSide] = None, - view_shape: Optional[OutlinedBorder] = None, - view_header_text_style: Optional[TextStyle] = None, - view_hint_text_style: Optional[TextStyle] = None, - view_size_constraints: Optional[BoxConstraints] = None, - view_header_height: OptionalNumber = None, - divider_color: Optional[ColorValue] = None, - capitalization: Optional[TextCapitalization] = None, - full_screen: Optional[bool] = None, - keyboard_type: Optional[KeyboardType] = None, - view_surface_tint_color: Optional[ColorValue] = None, - autofocus: Optional[bool] = None, - on_tap: OptionalControlEventCallable = None, - on_tap_outside_bar: OptionalControlEventCallable = None, - on_submit: OptionalControlEventCallable = None, - on_change: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.value = value - self.controls = controls - self.bar_leading = bar_leading - self.bar_trailing = bar_trailing - self.bar_hint_text = bar_hint_text - self.bar_bgcolor = bar_bgcolor - self.bar_overlay_color = bar_overlay_color - self.view_leading = view_leading - self.view_trailing = view_trailing - self.view_elevation = view_elevation - self.view_bgcolor = view_bgcolor - self.view_hint_text = view_hint_text - self.view_side = view_side - self.view_shape = view_shape - self.view_header_text_style = view_header_text_style - self.view_hint_text_style = view_hint_text_style - self.divider_color = divider_color - self.full_screen = full_screen - self.capitalization = capitalization - self.on_focus = on_focus - self.on_blur = on_blur - self.on_tap = on_tap - self.on_submit = on_submit - self.on_change = on_change - self.on_tap_outside_bar = on_tap_outside_bar - self.keyboard_type = keyboard_type - self.view_surface_tint_color = view_surface_tint_color - self.autofocus = autofocus - self.view_header_height = view_header_height - self.view_size_constraints = view_size_constraints - self.bar_surface_tint_color = bar_surface_tint_color - self.bar_elevation = bar_elevation - self.bar_border_side = bar_border_side - self.bar_shape = bar_shape - self.bar_text_style = bar_text_style - self.bar_hint_text_style = bar_hint_text_style - self.bar_padding = bar_padding - self.bar_scroll_padding = bar_scroll_padding - self.bar_shadow_color = bar_shadow_color - - def _get_control_name(self): - return "searchbar" - - def __contains__(self, item): - return item in self.__controls - - def before_update(self): - super().before_update() - self._set_attr_json("barBgcolor", self.__bar_bgcolor, wrap_attr_dict=True) - self._set_attr_json( - "barOverlayColor", self.__bar_overlay_color, wrap_attr_dict=True - ) - self._set_attr_json( - "barHintTextStyle", self.__bar_hint_text_style, wrap_attr_dict=True - ) - self._set_attr_json( - "barSurfaceTintColor", self.__bar_surface_tint_color, wrap_attr_dict=True - ) - self._set_attr_json("barElevation", self.__bar_elevation, wrap_attr_dict=True) - self._set_attr_json( - "barBorderSide", self.__bar_border_side, wrap_attr_dict=True - ) - self._set_attr_json("barShape", self.__bar_shape, wrap_attr_dict=True) - self._set_attr_json("barTextStyle", self.__bar_text_style, wrap_attr_dict=True) - self._set_attr_json("barPadding", self.__bar_padding, wrap_attr_dict=True) - self._set_attr_json( - "barShadowColor", self.__bar_shadow_color, wrap_attr_dict=True - ) - self._set_attr_json("viewShape", self.__view_shape) - self._set_attr_json("viewHeaderTextStyle", self.__view_header_text_style) - self._set_attr_json("viewHintTextStyle", self.__view_hint_text_style) - self._set_attr_json("viewSide", self.__view_side) - self._set_attr_json("viewSizeConstraints", self.__view_size_constraints) - self._set_attr_json("barScrollPadding", self.__bar_scroll_padding) - - def _get_children(self): - children = [] - if self.__bar_leading: - self.__bar_leading._set_attr_internal("n", "barLeading") - children.append(self.__bar_leading) - if self.__view_leading: - self.__view_leading._set_attr_internal("n", "viewLeading") - children.append(self.__view_leading) - for i in self.__bar_trailing: - i._set_attr_internal("n", "barTrailing") - children.append(i) - for i in self.__view_trailing: - i._set_attr_internal("n", "viewTrailing") - children.append(i) - for i in self.__controls: - i._set_attr_internal("n", "controls") - children.append(i) - return children - - # Public methods - def focus(self): - self._set_attr_json("focus", str(time.time())) - self.update() - - def blur(self): - self._set_attr_json("blur", str(time.time())) - self.update() - - def open_view(self): - m = { - "n": "openView", - "i": str(time.time()), - "p": {}, - } - self._set_attr_json("method", m) - self.update() - - def close_view(self, text: str = ""): - m = { - "n": "closeView", - "i": str(time.time()), - "p": {"text": text}, - } - self.value = text - self._set_attr_json("method", m) - self.update() - - # bar_leading - @property - def bar_leading(self) -> Optional[Control]: - return self.__bar_leading - - @bar_leading.setter - def bar_leading(self, value: Optional[Control]): - self.__bar_leading = value - - # bar_trailing - @property - def bar_trailing(self) -> Optional[List[Control]]: - return self.__bar_trailing - - @bar_trailing.setter - def bar_trailing(self, value: Optional[List[Control]]): - self.__bar_trailing = value if value is not None else [] - - # bar_bgcolor - @property - def bar_bgcolor(self) -> ControlStateValue[str]: - return self.__bar_bgcolor - - @bar_bgcolor.setter - def bar_bgcolor(self, value: ControlStateValue[str]): - self.__bar_bgcolor = value - - # bar_overlay_color - @property - def bar_overlay_color(self) -> ControlStateValue[str]: - return self.__bar_overlay_color - - @bar_overlay_color.setter - def bar_overlay_color(self, value: ControlStateValue[str]): - self.__bar_overlay_color = value - - # bar_shadow_color - @property - def bar_shadow_color(self) -> Union[None, str, Dict[ControlState, str]]: - return self.__bar_shadow_color - - @bar_shadow_color.setter - def bar_shadow_color(self, value: Union[None, str, Dict[ControlState, str]]): - self.__bar_shadow_color = value - - # bar_surface_tint_color - @property - def bar_surface_tint_color(self) -> Union[None, str, Dict[ControlState, str]]: - return self.__bar_surface_tint_color - - @bar_surface_tint_color.setter - def bar_surface_tint_color(self, value: Union[None, str, Dict[ControlState, str]]): - self.__bar_surface_tint_color = value - - # bar_elevation - @property - def bar_elevation(self) -> Union[OptionalNumber, Dict[ControlState, Number]]: - return self.__bar_elevation - - @bar_elevation.setter - def bar_elevation(self, value: Union[OptionalNumber, Dict[ControlState, Number]]): - self.__bar_elevation = value - - # bar_border_side - @property - def bar_border_side( - self, - ) -> ControlStateValue[BorderSide]: - return self.__bar_border_side - - @bar_border_side.setter - def bar_border_side(self, value: ControlStateValue[BorderSide]): - self.__bar_border_side = value - - # bar_shape - @property - def bar_shape( - self, - ) -> ControlStateValue[OutlinedBorder]: - return self.__bar_shape - - @bar_shape.setter - def bar_shape(self, value: ControlStateValue[OutlinedBorder]): - self.__bar_shape = value - - # bar_text_style - @property - def bar_text_style(self) -> ControlStateValue[TextStyle]: - return self.__bar_text_style - - @bar_text_style.setter - def bar_text_style(self, value: ControlStateValue[TextStyle]): - self.__bar_text_style = value - - # bar_hint_text_style - @property - def bar_hint_text_style( - self, - ) -> ControlStateValue[TextStyle]: - return self.__bar_hint_text_style - - @bar_hint_text_style.setter - def bar_hint_text_style(self, value: ControlStateValue[TextStyle]): - self.__bar_hint_text_style = value - - # bar_padding - @property - def bar_padding(self) -> ControlStateValue[PaddingValue]: - return self.__bar_padding - - @bar_padding.setter - def bar_padding(self, value: ControlStateValue[PaddingValue]): - self.__bar_padding = value - - # view_leading - @property - def view_leading(self) -> Optional[Control]: - return self.__view_leading - - @view_leading.setter - def view_leading(self, value: Optional[Control]): - self.__view_leading = value - - # view_surface_tint_color - @property - def view_surface_tint_color(self) -> Optional[ColorValue]: - return self.__view_surface_tint_color - - @view_surface_tint_color.setter - def view_surface_tint_color(self, value: Optional[ColorValue]): - self.__view_surface_tint_color = value - self._set_enum_attr("viewSurfaceTintColor", value, ColorEnums) - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # view_trailing - @property - def view_trailing(self) -> Optional[List[Control]]: - return self.__view_trailing - - @view_trailing.setter - def view_trailing(self, value: Optional[List[Control]]): - self.__view_trailing = value if value is not None else [] - - # view_elevation - @property - def view_elevation(self) -> OptionalNumber: - return self._get_attr("viewElevation") - - @view_elevation.setter - def view_elevation(self, value: OptionalNumber): - self._set_attr("viewElevation", value) - - # view_header_height - @property - def view_header_height(self) -> OptionalNumber: - return self._get_attr("viewHeaderHeight") - - @view_header_height.setter - def view_header_height(self, value: OptionalNumber): - self._set_attr("viewHeaderHeight", value) - - # view_bgcolor - @property - def view_bgcolor(self) -> Optional[str]: - return self.__view_bgcolor - - @view_bgcolor.setter - def view_bgcolor(self, value: Optional[str]): - self.__view_bgcolor = value - self._set_enum_attr("viewBgcolor", value, ColorEnums) - - # divider_color - @property - def divider_color(self) -> Optional[ColorValue]: - return self.__divider_color - - @divider_color.setter - def divider_color(self, value: Optional[ColorValue]): - self.__divider_color = value - self._set_enum_attr("dividerColor", value, ColorEnums) - - # bar_hint_text - @property - def bar_hint_text(self) -> Optional[str]: - return self._get_attr("barHintText") - - @bar_hint_text.setter - def bar_hint_text(self, value: Optional[str]): - self._set_attr("barHintText", value) - - # view_hint_text - @property - def view_hint_text(self) -> Optional[str]: - return self._get_attr("viewHintText") - - @view_hint_text.setter - def view_hint_text(self, value: Optional[str]): - self._set_attr("viewHintText", value) - - # view_shape - @property - def view_shape(self) -> Optional[OutlinedBorder]: - return self.__view_shape - - @view_shape.setter - def view_shape(self, value: Optional[OutlinedBorder]): - self.__view_shape = value - - # view_side - @property - def view_side(self) -> Optional[BorderSide]: - return self.__view_side - - @view_side.setter - def view_side(self, value: Optional[BorderSide]): - self.__view_side = value - - # view_size_constraints - @property - def view_size_constraints(self) -> Optional[BoxConstraints]: - return self.__view_size_constraints - - @view_size_constraints.setter - def view_size_constraints(self, value: Optional[BoxConstraints]): - self.__view_size_constraints = value - - # bar_scroll_padding - @property - def bar_scroll_padding(self) -> Optional[PaddingValue]: - return self.__bar_scroll_padding - - @bar_scroll_padding.setter - def bar_scroll_padding(self, value: Optional[PaddingValue]): - self.__bar_scroll_padding = value - - # full_screen - @property - def full_screen(self) -> bool: - return self._get_attr("fullScreen", data_type="bool", def_value=False) - - @full_screen.setter - def full_screen(self, value: Optional[bool]): - self._set_attr("fullScreen", value) - - # capitalization - @property - def capitalization(self) -> TextCapitalization: - return self.__capitalization - - @capitalization.setter - def capitalization(self, value: TextCapitalization): - self.__capitalization = value - self._set_enum_attr("capitalization", value, TextCapitalization) - - # keyboard_type - @property - def keyboard_type(self) -> KeyboardType: - return self.__keyboard_type - - @keyboard_type.setter - def keyboard_type(self, value: KeyboardType): - self.__keyboard_type = value - self._set_enum_attr("keyboardType", value, KeyboardType) - - # view_header_text_style - @property - def view_header_text_style(self): - return self.__view_header_text_style - - @view_header_text_style.setter - def view_header_text_style(self, value: Optional[TextStyle]): - self.__view_header_text_style = value - - # view_hint_text_style - @property - def view_hint_text_style(self): - return self.__view_hint_text_style - - @view_hint_text_style.setter - def view_hint_text_style(self, value: Optional[TextStyle]): - self.__view_hint_text_style = value - - # controls - @property - def controls(self): - return self.__controls - - @controls.setter - def controls(self, value: Optional[Sequence[Control]]): - self.__controls = list(value) if value is not None else [] - - # value - @property - def value(self) -> Optional[str]: - return self._get_attr("value", def_value="") - - @value.setter - def value(self, value: Optional[str]): - self._set_attr("value", value) - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - self._set_attr("onchange", True if handler is not None else None) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) - - # on_tap - @property - def on_tap(self) -> OptionalControlEventCallable: - return self._get_event_handler("tap") - - @on_tap.setter - def on_tap(self, handler: OptionalControlEventCallable): - self._add_event_handler("tap", handler) - self._set_attr("ontap", True if handler is not None else None) - - # on_tap_outside_bar - @property - def on_tap_outside_bar(self) -> OptionalControlEventCallable: - return self._get_event_handler("tapOutsideBar") - - @on_tap_outside_bar.setter - def on_tap_outside_bar(self, handler: OptionalControlEventCallable): - self._add_event_handler("tapOutsideBar", handler) - self._set_attr("onTapOutsideBar", True if handler is not None else None) - - # on_submit - @property - def on_submit(self) -> OptionalControlEventCallable: - return self._get_event_handler("submit") - - @on_submit.setter - def on_submit(self, handler: OptionalControlEventCallable): - self._add_event_handler("submit", handler) - self._set_attr("onsubmit", True if handler is not None else None) diff --git a/sdk/python/packages/flet/src/flet/core/segmented_button.py b/sdk/python/packages/flet/src/flet/core/segmented_button.py deleted file mode 100644 index dc6b97e69..000000000 --- a/sdk/python/packages/flet/src/flet/core/segmented_button.py +++ /dev/null @@ -1,336 +0,0 @@ -import json -from typing import Any, List, Optional, Set, Union - -from flet.core.alignment import Axis -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.buttons import ButtonStyle -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class Segment(Control): - def __init__( - self, - value: str, - icon: Optional[Control] = None, - label: Optional[Control] = None, - # - # Control - # - ref: Optional[Ref] = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - tooltip: Optional[str] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.value = value - self.label = label - self.icon = icon - self.tooltip = tooltip - - def _get_control_name(self): - return "segment" - - def _get_children(self): - children = [] - if self.__label: - self.__label._set_attr_internal("n", "label") - children.append(self.__label) - if self.__icon: - self.__icon._set_attr_internal("n", "icon") - children.append(self.__icon) - return children - - # label - @property - def label(self) -> Optional[Control]: - return self.__label - - @label.setter - def label(self, value: Optional[Control]): - self.__label = value - - # icon - @property - def icon(self) -> Optional[Control]: - return self.__icon - - @icon.setter - def icon(self, value: Optional[Control]): - self.__icon = value - - # value - @property - def value(self) -> str: - return self._get_attr("value") - - @value.setter - def value(self, value: str): - self._set_attr("value", value) - - # tooltip - @property - def tooltip(self) -> Optional[str]: - return self._get_attr("tooltip") - - @tooltip.setter - def tooltip(self, value: Optional[str]): - self._set_attr("tooltip", value) - - -class SegmentedButton(ConstrainedControl): - """ - A segmented button control. - - ----- - - Online docs: https://flet.dev/docs/controls/segmentedbutton - """ - - def __init__( - self, - segments: List[Segment], - style: Optional[ButtonStyle] = None, - allow_empty_selection: Optional[bool] = None, - allow_multiple_selection: Optional[bool] = None, - selected: Optional[Set] = None, - selected_icon: Optional[Control] = None, - show_selected_icon: Optional[bool] = None, - direction: Optional[Axis] = None, - padding: Optional[PaddingValue] = None, - on_change: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - visible=visible, - disabled=disabled, - data=data, - ) - - self.segments = segments - self.show_selected_icon = show_selected_icon - self.allow_multiple_selection = allow_multiple_selection - self.allow_empty_selection = allow_empty_selection - self.selected_icon = selected_icon - self.selected = selected - self.style = style - self.direction = direction - self.padding = padding - self.on_change = on_change - - def _get_control_name(self): - return "segmentedbutton" - - def before_update(self): - super().before_update() - assert any( - segment.visible for segment in self.__segments - ), "segments must have at minimum one visible Segment" - assert ( - len(self.selected) > 0 or self.allow_empty_selection - ), "allow_empty_selection must be True for selected to be empty" - assert ( - len(self.selected) < 2 or self.allow_multiple_selection - ), "allow_multiple_selection must be True for selected to have more than one item" - style = self.__style or ButtonStyle() - style.side = self._wrap_attr_dict(style.side) - style.shape = self._wrap_attr_dict(style.shape) - style.padding = self._wrap_attr_dict(style.padding) - self._set_attr_json("style", style) - self._set_attr_json("padding", self.__padding) - - def _get_children(self): - for segment in self.segments: - segment._set_attr_internal("n", "segment") - children: List[Control] = self.__segments - if self.__selected_icon: - self.__selected_icon._set_attr_internal("n", "selectedIcon") - children.append(self.__selected_icon) - return children - - def __contains__(self, item): - return item in self.__segments - - # style - @property - def style(self) -> Optional[ButtonStyle]: - return self.__style - - @style.setter - def style(self, value: Optional[ButtonStyle]): - self.__style = value - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - - # segments - @property - def segments(self) -> List[Segment]: - return self.__segments - - @segments.setter - def segments(self, value: List[Segment]): - self.__segments = value - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # allow_empty_selection - @property - def allow_empty_selection(self) -> bool: - return self._get_attr("allowEmptySelection", data_type="bool", def_value=False) - - @allow_empty_selection.setter - def allow_empty_selection(self, value: Optional[bool]): - self._set_attr("allowEmptySelection", value) - - # allow_multiple_selection - @property - def allow_multiple_selection(self) -> bool: - return self._get_attr( - "allowMultipleSelection", data_type="bool", def_value=False - ) - - @allow_multiple_selection.setter - def allow_multiple_selection(self, value: Optional[bool]): - self._set_attr("allowMultipleSelection", value) - - # selected - @property - def selected(self) -> Optional[Set]: - s = self._get_attr("selected") - return set(json.loads(s)) if s else s - - @selected.setter - def selected(self, value: Optional[Set]): - self._set_attr( - "selected", - ( - json.dumps(list(value), separators=(",", ":")) - if value is not None - else None - ), - ) - - # show_selected_icon - @property - def show_selected_icon(self) -> bool: - return self._get_attr("showSelectedIcon", data_type="bool", def_value=True) - - @show_selected_icon.setter - def show_selected_icon(self, value: Optional[bool]): - self._set_attr("showSelectedIcon", value) - - # direction - @property - def direction(self) -> Optional[Axis]: - return self.__direction - - @direction.setter - def direction(self, value: Optional[Axis]): - self.__direction = value - self._set_enum_attr("direction", value, Axis) - - # selected_icon - @property - def selected_icon(self) -> Optional[Control]: - return self.__selected_icon - - @selected_icon.setter - def selected_icon(self, value: Optional[Control]): - self.__selected_icon = value diff --git a/sdk/python/packages/flet/src/flet/core/selection_area.py b/sdk/python/packages/flet/src/flet/core/selection_area.py deleted file mode 100644 index 43c8c6ea2..000000000 --- a/sdk/python/packages/flet/src/flet/core/selection_area.py +++ /dev/null @@ -1,74 +0,0 @@ -from typing import Any, Optional - -from flet.core.control import Control -from flet.core.ref import Ref -from flet.core.types import OptionalControlEventCallable - - -class SelectionArea(Control): - """ - Flet controls are not selectable by default. SelectionArea is used to enable selection for its child control. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.add( - ft.SelectionArea( - content=ft.Column([ft.Text("Selectable text"), ft.Text("Also selectable")]) - ) - ) - page.add(ft.Text("Not selectable")) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/selectionarea - """ - - def __init__( - self, - content: Control, - on_change: OptionalControlEventCallable = None, - # - # Control - # - ref: Optional[Ref] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - data=data, - ) - - self.content = content - self.on_change = on_change - - def _get_control_name(self): - return "selectionarea" - - def _get_children(self): - self.__content._set_attr_internal("n", "content") - return [self.__content] - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) diff --git a/sdk/python/packages/flet/src/flet/core/semantics.py b/sdk/python/packages/flet/src/flet/core/semantics.py deleted file mode 100644 index cbc620ed7..000000000 --- a/sdk/python/packages/flet/src/flet/core/semantics.py +++ /dev/null @@ -1,639 +0,0 @@ -from typing import Any, Optional - -from flet.core.badge import BadgeValue -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.types import OptionalControlEventCallable - - -class Semantics(Control): - """ - A control that annotates the control tree with a description of the meaning of the widgets. - - Used by accessibility tools, search engines, and other semantic analysis software to determine the meaning of the application. - - ----- - - Online docs: https://flet.dev/docs/controls/semantics - """ - - def __init__( - self, - content: Optional[Control] = None, - label: Optional[str] = None, - expanded: Optional[bool] = None, - hidden: Optional[bool] = None, - selected: Optional[bool] = None, - button: Optional[bool] = None, - obscured: Optional[bool] = None, - multiline: Optional[bool] = None, - focusable: Optional[bool] = None, - read_only: Optional[bool] = None, - focus: Optional[bool] = None, - slider: Optional[bool] = None, - tooltip: Optional[str] = None, - badge: Optional[BadgeValue] = None, - toggled: Optional[bool] = None, - max_value_length: OptionalNumber = None, - checked: Optional[bool] = None, - value: Optional[str] = None, - increased_value: Optional[str] = None, - decreased_value: Optional[str] = None, - hint_text: Optional[str] = None, - on_tap_hint_text: Optional[str] = None, - current_value_length: Optional[int] = None, - heading_level: Optional[int] = None, - exclude_semantics: Optional[bool] = None, - mixed: Optional[bool] = None, - on_long_press_hint_text: Optional[str] = None, - container: Optional[bool] = None, - live_region: Optional[bool] = None, - textfield: Optional[bool] = None, - link: Optional[bool] = None, - header: Optional[bool] = None, - image: Optional[bool] = None, - on_tap: OptionalControlEventCallable = None, - on_double_tap: OptionalControlEventCallable = None, - on_increase: OptionalControlEventCallable = None, - on_decrease: OptionalControlEventCallable = None, - on_dismiss: OptionalControlEventCallable = None, - on_scroll_left: OptionalControlEventCallable = None, - on_scroll_right: OptionalControlEventCallable = None, - on_scroll_up: OptionalControlEventCallable = None, - on_scroll_down: OptionalControlEventCallable = None, - on_copy: OptionalControlEventCallable = None, - on_cut: OptionalControlEventCallable = None, - on_paste: OptionalControlEventCallable = None, - on_long_press: OptionalControlEventCallable = None, - on_move_cursor_forward_by_character: OptionalControlEventCallable = None, - on_move_cursor_backward_by_character: OptionalControlEventCallable = None, - on_did_gain_accessibility_focus: OptionalControlEventCallable = None, - on_did_lose_accessibility_focus: OptionalControlEventCallable = None, - on_set_text: OptionalControlEventCallable = None, - # - # Control - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - visible=visible, - disabled=disabled, - data=data, - badge=badge, - ) - - self.content = content - self.label = label - self.expanded = expanded - self.hidden = hidden - self.selected = selected - self.button = button - self.obscured = obscured - self.multiline = multiline - self.focusable = focusable - self.read_only = read_only - self.focus = focus - self.slider = slider - self.tooltip = tooltip - self.toggled = toggled - self.max_value_length = max_value_length - self.checked = checked - self.value = value - self.increased_value = increased_value - self.decreased_value = decreased_value - self.hint_text = hint_text - self.on_tap_hint_text = on_tap_hint_text - self.on_long_press_hint_text = on_long_press_hint_text - self.container = container - self.live_region = live_region - self.textfield = textfield - self.link = link - self.header = header - self.image = image - self.on_tap = on_tap - self.on_double_tap = on_double_tap - self.on_increase = on_increase - self.on_decrease = on_decrease - self.on_dismiss = on_dismiss - self.on_scroll_left = on_scroll_left - self.on_scroll_right = on_scroll_right - self.on_scroll_up = on_scroll_up - self.on_scroll_down = on_scroll_down - self.on_copy = on_copy - self.on_cut = on_cut - self.on_paste = on_paste - self.on_long_press = on_long_press - self.on_move_cursor_forward_by_character = on_move_cursor_forward_by_character - self.on_move_cursor_backward_by_character = on_move_cursor_backward_by_character - self.on_did_gain_accessibility_focus = on_did_gain_accessibility_focus - self.on_did_lose_accessibility_focus = on_did_lose_accessibility_focus - self.current_value_length = current_value_length - self.heading_level = heading_level - self.exclude_semantics = exclude_semantics - self.mixed = mixed - self.on_set_text = on_set_text - - def _get_control_name(self): - return "semantics" - - def _get_children(self): - children = [] - if self.__content is not None: - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - return children - - # label - @property - def label(self) -> Optional[str]: - return self._get_attr("label") - - @label.setter - def label(self, value: Optional[str]): - self._set_attr("label", value) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # expanded - @property - def expanded(self) -> Optional[bool]: - return self._get_attr("expanded", data_type="bool") - - @expanded.setter - def expanded(self, value: Optional[bool]): - self._set_attr("expanded", value) - - # hidden - @property - def hidden(self): - return self._get_attr("hidden") - - @hidden.setter - def hidden(self, value: Optional[bool]): - self._set_attr("hidden", value) - - # textfield - @property - def textfield(self) -> Optional[bool]: - return self._get_attr("textfield", data_type="bool") - - @textfield.setter - def textfield(self, value: Optional[bool]): - self._set_attr("textfield", value) - - # link - @property - def link(self) -> Optional[bool]: - return self._get_attr("link", data_type="bool") - - @link.setter - def link(self, value: Optional[bool]): - self._set_attr("link", value) - - # image - @property - def image(self) -> Optional[bool]: - return self._get_attr("image", data_type="bool") - - @image.setter - def image(self, value: Optional[bool]): - self._set_attr("image", value) - - # header - @property - def header(self) -> Optional[bool]: - return self._get_attr("header", data_type="bool") - - @header.setter - def header(self, value: Optional[bool]): - self._set_attr("header", value) - - # selected - @property - def selected(self) -> Optional[bool]: - return self._get_attr("selected", data_type="bool") - - @selected.setter - def selected(self, value: Optional[bool]): - self._set_attr("selected", value) - - # button - @property - def button(self) -> Optional[bool]: - return self._get_attr("button", data_type="bool") - - @button.setter - def button(self, value: Optional[bool]): - self._set_attr("button", value) - - # obscured - @property - def obscured(self) -> Optional[bool]: - return self._get_attr("obscured", data_type="bool") - - @obscured.setter - def obscured(self, value: Optional[bool]): - self._set_attr("obscured", value) - - # multiline - @property - def multiline(self) -> Optional[bool]: - return self._get_attr("multiline", data_type="bool") - - @multiline.setter - def multiline(self, value: Optional[bool]): - self._set_attr("multiline", value) - - # focusable - @property - def focusable(self) -> Optional[bool]: - return self._get_attr("focusable", data_type="bool") - - @focusable.setter - def focusable(self, value: Optional[bool]): - self._set_attr("focusable", value) - - # read_only - @property - def read_only(self) -> Optional[bool]: - return self._get_attr("readOnly", data_type="bool") - - @read_only.setter - def read_only(self, value: Optional[bool]): - self._set_attr("readOnly", value) - - # focused - @property - def focused(self) -> Optional[bool]: - return self._get_attr("focused", data_type="bool") - - @focused.setter - def focused(self, value: Optional[bool]): - self._set_attr("focused", value) - - # mixed - @property - def mixed(self) -> Optional[bool]: - return self._get_attr("mixed", data_type="bool") - - @mixed.setter - def mixed(self, value: Optional[bool]): - self._set_attr("mixed", value) - - # exclude_semantics - @property - def exclude_semantics(self) -> bool: - return self._get_attr("excludeSemantics", data_type="bool", def_value=False) - - @exclude_semantics.setter - def exclude_semantics(self, value: Optional[bool]): - self._set_attr("excludeSemantics", value) - - # heading_level - @property - def heading_level(self) -> Optional[int]: - return self._get_attr("headingLevel", data_type="int") - - @heading_level.setter - def heading_level(self, value: Optional[int]): - self._set_attr("headingLevel", value) - - # current_value_length - @property - def current_value_length(self) -> Optional[int]: - return self._get_attr("currentValueLength", data_type="int") - - @current_value_length.setter - def current_value_length(self, value: Optional[int]): - self._set_attr("currentValueLength", value) - - # slider - @property - def slider(self) -> Optional[bool]: - return self._get_attr("slider", data_type="bool") - - @slider.setter - def slider(self, value: Optional[bool]): - self._set_attr("slider", value) - - # tooltip - @property - def tooltip(self) -> Optional[str]: - return self._get_attr("tooltip") - - @tooltip.setter - def tooltip(self, value: Optional[str]): - self._set_attr("tooltip", value) - - # toggled - @property - def toggled(self) -> Optional[bool]: - return self._get_attr("toggled", data_type="bool") - - @toggled.setter - def toggled(self, value: Optional[bool]): - self._set_attr("toggled", value) - - # max_value_length - @property - def max_value_length(self) -> OptionalNumber: - return self._get_attr("maxValueLength") - - @max_value_length.setter - def max_value_length(self, value: OptionalNumber): - self._set_attr("maxValueLength", value) - - # checked - @property - def checked(self) -> Optional[bool]: - return self._get_attr("checked", data_type="bool") - - @checked.setter - def checked(self, value: Optional[bool]): - self._set_attr("checked", value) - - # value - @property - def value(self) -> Optional[str]: - return self._get_attr("value") - - @value.setter - def value(self, value: Optional[str]): - self._set_attr("value", value) - - # increased_value - @property - def increased_value(self) -> Optional[str]: - return self._get_attr("increasedValue") - - @increased_value.setter - def increased_value(self, value: Optional[str]): - self._set_attr("increasedValue", value) - - # decreased_value - @property - def decreased_value(self) -> Optional[str]: - return self._get_attr("decreasedValue") - - @decreased_value.setter - def decreased_value(self, value: Optional[str]): - self._set_attr("decreasedValue", value) - - # hint_text - @property - def hint_text(self) -> Optional[str]: - return self._get_attr("hintText") - - @hint_text.setter - def hint_text(self, value: Optional[str]): - self._set_attr("hintText", value) - - # on_long_press_hint_text - @property - def on_long_press_hint_text(self): - return self._get_attr("onLongPressHintText") - - @on_long_press_hint_text.setter - def on_long_press_hint_text(self, value: Optional[bool]): - self._set_attr("onLongPressHintText", value) - - # on_tap_hint_text - @property - def on_tap_hint_text(self): - return self._get_attr("onTapHintText") - - @on_tap_hint_text.setter - def on_tap_hint_text(self, value: Optional[bool]): - self._set_attr("onTapHintText", value) - - # container - @property - def container(self) -> Optional[bool]: - return self._get_attr("container", data_type="bool") - - @container.setter - def container(self, value: Optional[bool]): - self._set_attr("container", value) - - # live_region - @property - def live_region(self) -> Optional[bool]: - return self._get_attr("liveRegion", data_type="bool") - - @live_region.setter - def live_region(self, value: Optional[bool]): - self._set_attr("liveRegion", value) - - # on_tap - @property - def on_tap(self) -> OptionalControlEventCallable: - return self._get_event_handler("tap") - - @on_tap.setter - def on_tap(self, handler: OptionalControlEventCallable): - self._add_event_handler("tap", handler) - self._set_attr("onTap", True if handler is not None else None) - - # on_double_tap - @property - def on_double_tap(self) -> OptionalControlEventCallable: - return self._get_event_handler("double_tap") - - @on_double_tap.setter - def on_double_tap(self, handler: OptionalControlEventCallable): - self._add_event_handler("double_tap", handler) - self._set_attr("onDoubleTap", True if handler is not None else None) - - # on_increase - @property - def on_increase(self) -> OptionalControlEventCallable: - return self._get_event_handler("increase") - - @on_increase.setter - def on_increase(self, handler: OptionalControlEventCallable): - self._add_event_handler("increase", handler) - self._set_attr("onIncrease", True if handler is not None else None) - - # on_decrease - @property - def on_decrease(self) -> OptionalControlEventCallable: - return self._get_event_handler("decrease") - - @on_decrease.setter - def on_decrease(self, handler: OptionalControlEventCallable): - self._add_event_handler("decrease", handler) - self._set_attr("onDecrease", True if handler is not None else None) - - # on_dismiss - @property - def on_dismiss(self) -> OptionalControlEventCallable: - return self._get_event_handler("dismiss") - - @on_dismiss.setter - def on_dismiss(self, handler: OptionalControlEventCallable): - self._add_event_handler("dismiss", handler) - self._set_attr("onDismiss", True if handler is not None else None) - - # on_scroll_left - @property - def on_scroll_left(self) -> OptionalControlEventCallable: - return self._get_event_handler("scroll_left") - - @on_scroll_left.setter - def on_scroll_left(self, handler: OptionalControlEventCallable): - self._add_event_handler("scroll_left", handler) - self._set_attr("onScrollLeft", True if handler is not None else None) - - # on_scroll_right - @property - def on_scroll_right(self) -> OptionalControlEventCallable: - return self._get_event_handler("scroll_right") - - @on_scroll_right.setter - def on_scroll_right(self, handler: OptionalControlEventCallable): - self._add_event_handler("scroll_right", handler) - self._set_attr("onScrollRight", True if handler is not None else None) - - # on_scroll_up - @property - def on_scroll_up(self) -> OptionalControlEventCallable: - return self._get_event_handler("scroll_up") - - @on_scroll_up.setter - def on_scroll_up(self, handler: OptionalControlEventCallable): - self._add_event_handler("scroll_up", handler) - self._set_attr("onScrollUp", True if handler is not None else None) - - # on_scroll_down - @property - def on_scroll_down(self) -> OptionalControlEventCallable: - return self._get_event_handler("scroll_down") - - @on_scroll_down.setter - def on_scroll_down(self, handler: OptionalControlEventCallable): - self._add_event_handler("scroll_down", handler) - self._set_attr("onScrollDown", True if handler is not None else None) - - # on_copy - @property - def on_copy(self) -> OptionalControlEventCallable: - return self._get_event_handler("copy") - - @on_copy.setter - def on_copy(self, handler: OptionalControlEventCallable): - self._add_event_handler("copy", handler) - self._set_attr("onCopy", True if handler is not None else None) - - # on_cut - @property - def on_cut(self) -> OptionalControlEventCallable: - return self._get_event_handler("cut") - - @on_cut.setter - def on_cut(self, handler: OptionalControlEventCallable): - self._add_event_handler("cut", handler) - self._set_attr("onCut", True if handler is not None else None) - - # on_paste - @property - def on_paste(self) -> OptionalControlEventCallable: - return self._get_event_handler("paste") - - @on_paste.setter - def on_paste(self, handler: OptionalControlEventCallable): - self._add_event_handler("paste", handler) - self._set_attr("onPaste", True if handler is not None else None) - - # on_long_press - @property - def on_long_press(self) -> OptionalControlEventCallable: - return self._get_event_handler("long_press") - - @on_long_press.setter - def on_long_press(self, handler: OptionalControlEventCallable): - self._add_event_handler("long_press", handler) - self._set_attr("onLongPress", True if handler is not None else None) - - # on_move_cursor_forward_by_character - @property - def on_move_cursor_forward_by_character( - self, - ) -> OptionalControlEventCallable: - return self._get_event_handler("move_cursor_forward_by_character") - - @on_move_cursor_forward_by_character.setter - def on_move_cursor_forward_by_character( - self, handler: OptionalControlEventCallable - ): - self._add_event_handler("move_cursor_forward_by_character", handler) - self._set_attr( - "onMoveCursorForwardByCharacter", True if handler is not None else None - ) - - # on_move_cursor_backward_by_character - @property - def on_move_cursor_backward_by_character( - self, - ) -> OptionalControlEventCallable: - return self._get_event_handler("move_cursor_backward_by_character") - - @on_move_cursor_backward_by_character.setter - def on_move_cursor_backward_by_character( - self, handler: OptionalControlEventCallable - ): - self._add_event_handler("move_cursor_backward_by_character", handler) - self._set_attr( - "onMoveCursorBackwardByCharacter", True if handler is not None else None - ) - - # on_did_gain_accessibility_focus - @property - def on_did_gain_accessibility_focus( - self, - ) -> OptionalControlEventCallable: - return self._get_event_handler("did_gain_accessibility_focus") - - @on_did_gain_accessibility_focus.setter - def on_did_gain_accessibility_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("did_gain_accessibility_focus", handler) - self._set_attr( - "onDidGainAccessibilityFocus", True if handler is not None else None - ) - - # on_did_lose_accessibility_focus - @property - def on_did_lose_accessibility_focus( - self, - ) -> OptionalControlEventCallable: - return self._get_event_handler("did_lose_accessibility_focus") - - @on_did_lose_accessibility_focus.setter - def on_did_lose_accessibility_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("did_lose_accessibility_focus", handler) - self._set_attr( - "onDidLoseAccessibilityFocus", True if handler is not None else None - ) - - # on_set_text - @property - def on_set_text( - self, - ) -> OptionalControlEventCallable: - return self._get_event_handler("set_text") - - @on_set_text.setter - def on_set_text(self, handler: OptionalControlEventCallable): - self._add_event_handler("set_text", handler) - self._set_attr("setText", True if handler is not None else None) diff --git a/sdk/python/packages/flet/src/flet/core/semantics_service.py b/sdk/python/packages/flet/src/flet/core/semantics_service.py deleted file mode 100644 index a51a59b28..000000000 --- a/sdk/python/packages/flet/src/flet/core/semantics_service.py +++ /dev/null @@ -1,51 +0,0 @@ -from enum import Enum -from typing import Any, Optional - -from flet.core.control import Control -from flet.core.ref import Ref - - -class Assertiveness(Enum): - POLITE = "polite" - ASSERTIVE = "assertive" - - -class SemanticsService(Control): - def __init__( - self, - ref: Optional[Ref] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - data=data, - ) - - def _get_control_name(self): - return "semanticsservice" - - def announce_message( - self, - message: str, - rtl: bool = False, - assertiveness: Assertiveness = Assertiveness.POLITE, - ): - self.invoke_method( - "announce_message", - arguments={ - "message": message, - "rtl": str(rtl), - "assertiveness": assertiveness.value - if isinstance(assertiveness, Assertiveness) - else str(assertiveness), - }, - ) - - def announce_tooltip(self, message: str): - self.invoke_method( - "announce_tooltip", - arguments={ - "message": message, - }, - ) diff --git a/sdk/python/packages/flet/src/flet/core/shader_mask.py b/sdk/python/packages/flet/src/flet/core/shader_mask.py deleted file mode 100644 index 8d641eb22..000000000 --- a/sdk/python/packages/flet/src/flet/core/shader_mask.py +++ /dev/null @@ -1,180 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.gradients import Gradient -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - BlendMode, - BorderRadiusValue, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class ShaderMask(ConstrainedControl): - """ - A control that applies a mask generated by a shader to its child. - - For example, ShaderMask can be used to gradually fade out the edge of a child by using a `LinearGradient` mask. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.add( - ft.Row( - [ - ft.ShaderMask( - ft.Image(src="https://picsum.photos/100/200?2"), - blend_mode=ft.BlendMode.DST_IN, - shader=ft.LinearGradient( - begin=ft.alignment.top_center, - end=ft.alignment.bottom_center, - colors=[ft.colors.BLACK, ft.colors.TRANSPARENT], - stops=[0.5, 1.0], - ), - border_radius=10, - ), - ] - ) - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/shadermask - """ - - def __init__( - self, - shader: Gradient, - content: Optional[Control] = None, - blend_mode: Optional[BlendMode] = None, - border_radius: Optional[BorderRadiusValue] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.content = content - self.blend_mode = blend_mode - self.shader = shader - self.border_radius = border_radius - - def _get_control_name(self): - return "shadermask" - - def before_update(self): - super().before_update() - self._set_attr_json("shader", self.__shader) - self._set_attr_json("borderRadius", self.__border_radius) - - def _get_children(self): - if self.__content: - self.__content._set_attr_internal("n", "content") - return [self.__content] - return [] - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # blend_mode - @property - def blend_mode(self) -> Optional[BlendMode]: - return self.__blend_mode - - @blend_mode.setter - def blend_mode(self, value: Optional[BlendMode]): - self.__blend_mode = value - self._set_enum_attr("blendMode", value, BlendMode) - - # shader - @property - def shader(self) -> Optional[Gradient]: - return self.__shader - - @shader.setter - def shader(self, value: Optional[Gradient]): - self.__shader = value - - # border_radius - @property - def border_radius(self) -> Optional[BorderRadiusValue]: - return self.__border_radius - - @border_radius.setter - def border_radius(self, value: Optional[BorderRadiusValue]): - self.__border_radius = value diff --git a/sdk/python/packages/flet/src/flet/core/shake_detector.py b/sdk/python/packages/flet/src/flet/core/shake_detector.py deleted file mode 100644 index 162d4eb3a..000000000 --- a/sdk/python/packages/flet/src/flet/core/shake_detector.py +++ /dev/null @@ -1,109 +0,0 @@ -from typing import Any, Optional - -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.types import OptionalControlEventCallable - - -class ShakeDetector(Control): - """ - Detects phone shakes. - - It is non-visual and should be added to `page.overlay` list. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - shd = ft.ShakeDetector( - minimum_shake_count=2, - shake_slop_time_ms=300, - shake_count_reset_time_ms=1000, - on_shake=lambda _: print("SHAKE DETECTED!"), - ) - page.overlay.append(shd) - - page.add(ft.Text("Program body")) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/shakedetector - """ - - def __init__( - self, - minimum_shake_count: Optional[int] = None, - shake_slop_time_ms: Optional[int] = None, - shake_count_reset_time_ms: Optional[int] = None, - shake_threshold_gravity: OptionalNumber = None, - on_shake: OptionalControlEventCallable = None, - # - # Control - # - ref: Optional[Ref] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - data=data, - ) - - self.minimum_shake_count = minimum_shake_count - self.shake_slop_time_ms = shake_slop_time_ms - self.shake_count_reset_time_ms = shake_count_reset_time_ms - self.shake_threshold_gravity = shake_threshold_gravity - self.on_shake = on_shake - - def _get_control_name(self): - return "shakedetector" - - # minimum_shake_count - @property - def minimum_shake_count(self) -> Optional[int]: - return self._get_attr("minimumShakeCount", data_type="int") - - @minimum_shake_count.setter - def minimum_shake_count(self, value: Optional[int]): - self._set_attr("minimumShakeCount", value) - - # shake_slop_time_ms - @property - def shake_slop_time_ms(self) -> Optional[int]: - return self._get_attr("shakeSlopTimeMS", data_type="int") - - @shake_slop_time_ms.setter - def shake_slop_time_ms(self, value: Optional[int]): - self._set_attr("shakeSlopTimeMS", value) - - # shake_count_reset_time_ms - @property - def shake_count_reset_time_ms(self) -> Optional[int]: - return self._get_attr("shakeCountResetTimeMs", data_type="int") - - @shake_count_reset_time_ms.setter - def shake_count_reset_time_ms(self, value: Optional[int]): - self._set_attr("shakeCountResetTimeMs", value) - - # shake_threshold_gravity - @property - def shake_threshold_gravity(self) -> OptionalNumber: - return self._get_attr("shakeThresholdGravity", data_type="float") - - @shake_threshold_gravity.setter - def shake_threshold_gravity(self, value: OptionalNumber): - self._set_attr("shakeThresholdGravity", value) - - # on_shake - @property - def on_shake(self) -> OptionalControlEventCallable: - return self._get_event_handler("shake") - - @on_shake.setter - def on_shake(self, handler: OptionalControlEventCallable): - self._add_event_handler("shake", handler) diff --git a/sdk/python/packages/flet/src/flet/core/size.py b/sdk/python/packages/flet/src/flet/core/size.py deleted file mode 100644 index 483a0ec1d..000000000 --- a/sdk/python/packages/flet/src/flet/core/size.py +++ /dev/null @@ -1,73 +0,0 @@ -from dataclasses import dataclass - -from flet.core.types import Number - -__all__ = [ - "Size", - "copy", - "square", - "from_width", - "from_height", - "from_radius", - "zero", - "infinite", -] - - -@dataclass(frozen=True) -class Size: - """ - A class representing a 2D size with width and height. - """ - - width: float - height: float - - @property - def aspect_ratio(self) -> float: - """Returns the aspect ratio (width / height).""" - if self.height != 0.0: - return self.width / self.height - if self.width > 0.0: - return float("inf") - if self.width < 0.0: - return float("-inf") - return 0.0 - - def is_infinite(self) -> bool: - """Checks if either dimension is infinite.""" - return self.width == float("inf") or self.height == float("inf") - - def is_finite(self) -> bool: - """Checks if both dimensions are finite.""" - return self.width != float("inf") and self.height != float("inf") - - -def copy(source: "Size") -> Size: - """Creates a copy of the given Size object.""" - return Size(source.width, source.height) - - -def square(dimension: Number) -> Size: - """Creates a square Size where width and height are the same.""" - return Size(dimension, dimension) - - -def from_width(width: Number) -> Size: - """Creates a Size with the given width and an infinite height.""" - return Size(width, float("inf")) - - -def from_height(height: Number) -> Size: - """Creates a Size with the given height and an infinite width.""" - return Size(float("inf"), height) - - -def from_radius(radius: Number) -> Size: - """Creates a square Size whose width and height are twice the given radius.""" - return Size(radius * 2.0, radius * 2.0) - - -# Constants -zero = Size(0.0, 0.0) -infinite = Size(float("inf"), float("inf")) diff --git a/sdk/python/packages/flet/src/flet/core/slider.py b/sdk/python/packages/flet/src/flet/core/slider.py deleted file mode 100644 index d64ea65f2..000000000 --- a/sdk/python/packages/flet/src/flet/core/slider.py +++ /dev/null @@ -1,397 +0,0 @@ -from enum import Enum -from typing import Any, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - ControlStateValue, - MouseCursor, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, - PaddingValue, -) -from flet.utils.deprecated import deprecated_property - - -class SliderInteraction(Enum): - TAP_AND_SLIDE = "tapAndSlide" - TAP_ONLY = "tapOnly" - SLIDE_ONLY = "slideOnly" - SLIDE_THUMB = "slideThumb" - - -class Slider(ConstrainedControl, AdaptiveControl): - """ - A slider provides a visual indication of adjustable content, as well as the current setting in the total range of content. - - Use a slider when you want people to set defined values (such as volume or brightness), or when people would benefit from instant feedback on the effect of setting changes. - - Example: - ``` - import flet as ft - - def main(page): - page.add( - ft.Text("Slider with value:"), - ft.Slider(value=0.3), - ft.Text("Slider with a custom range and label:"), - ft.Slider(min=0, max=100, divisions=10, label="{value}%")) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/slider - """ - - def __init__( - self, - value: OptionalNumber = None, - label: Optional[str] = None, - min: OptionalNumber = None, - max: OptionalNumber = None, - divisions: Optional[int] = None, - round: Optional[int] = None, - autofocus: Optional[bool] = None, - active_color: Optional[ColorValue] = None, - inactive_color: Optional[ColorValue] = None, - thumb_color: Optional[ColorValue] = None, - interaction: Optional[SliderInteraction] = None, - secondary_active_color: Optional[ColorValue] = None, - overlay_color: ControlStateValue[ColorValue] = None, - secondary_track_value: OptionalNumber = None, - mouse_cursor: Optional[MouseCursor] = None, - padding: Optional[PaddingValue] = None, - year_2023: Optional[bool] = None, - on_change: OptionalControlEventCallable = None, - on_change_start: OptionalControlEventCallable = None, - on_change_end: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl and AdaptiveControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.value = value - self.label = label - self.min = min - self.max = max - self.divisions = divisions - self.round = round - self.autofocus = autofocus - self.active_color = active_color - self.inactive_color = inactive_color - self.thumb_color = thumb_color - self.interaction = interaction - self.overlay_color = overlay_color - self.on_change = on_change - self.on_change_start = on_change_start - self.on_change_end = on_change_end - self.on_focus = on_focus - self.on_blur = on_blur - self.secondary_active_color = secondary_active_color - self.secondary_track_value = secondary_track_value - self.mouse_cursor = mouse_cursor - self.padding = padding - self.year_2023 = year_2023 - - def _get_control_name(self): - return "slider" - - def before_update(self): - super().before_update() - assert ( - self.min is None or self.max is None or self.min <= self.max - ), "min must be less than or equal to max" - assert ( - self.min is None or self.value is None or (self.value >= self.min) - ), "value must be greater than or equal to min" - assert ( - self.max is None or self.value is None or (self.value <= self.max) - ), "value must be less than or equal to max" - self._set_attr_json("overlayColor", self.__overlay_color, wrap_attr_dict=True) - self._set_attr_json("padding", self.__padding) - - # value - @property - def value(self) -> float: - return self._get_attr("value", data_type="float", def_value=self.min or 0.0) - - @value.setter - def value(self, value: OptionalNumber): - self._set_attr("value", value) - - # label - @property - def label(self) -> Optional[str]: - return self._get_attr("label") - - @label.setter - def label(self, value: Optional[str]): - self._set_attr("label", value) - - # year_2023 - @property - def year_2023(self) -> Optional[bool]: - return self._get_attr("year2023", data_type="bool", def_value=True) - - @year_2023.setter - def year_2023(self, value: Optional[bool]): - self._set_attr("year2023", value) - if value is not None: - deprecated_property( - name="year_2023", - version="0.27.0", - delete_version=None, # not known for now - reason="Set this flag to False to opt into the 2024 Slider appearance. In the future, this flag will default to False.", - ) - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # interaction - @property - def interaction(self) -> Optional[SliderInteraction]: - return self.__interaction - - @interaction.setter - def interaction(self, value: Optional[SliderInteraction]): - self.__interaction = value - self._set_enum_attr("interaction", value, SliderInteraction) - - # min - @property - def min(self) -> float: - return self._get_attr("min", data_type="float", def_value=0.0) - - @min.setter - def min(self, value: OptionalNumber): - self._set_attr("min", value) - - # secondary_track_value - @property - def secondary_track_value(self) -> OptionalNumber: - return self._get_attr("secondaryTrackValue", data_type="float") - - @secondary_track_value.setter - def secondary_track_value(self, value: OptionalNumber): - self._set_attr("secondaryTrackValue", value) - - # secondary_active_color - @property - def secondary_active_color(self) -> Optional[ColorValue]: - return self.__secondary_active_color - - @secondary_active_color.setter - def secondary_active_color(self, value: Optional[ColorValue]): - self.__secondary_active_color = value - self._set_enum_attr("secondaryActiveColor", value, ColorEnums) - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self._get_attr("mouseCursor") - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # max - @property - def max(self) -> float: - return self._get_attr("max", data_type="float", def_value=1.0) - - @max.setter - def max(self, value: OptionalNumber): - self._set_attr("max", value) - - # divisions - @property - def divisions(self) -> Optional[int]: - return self._get_attr("divisions") - - @divisions.setter - def divisions(self, value: Optional[int]): - self._set_attr("divisions", value) - - # round - @property - def round(self) -> int: - return self._get_attr("round", data_type="int", def_value=0) - - @round.setter - def round(self, value: Optional[int]): - self._set_attr("round", value) - - # overlay_color - @property - def overlay_color(self) -> ControlStateValue[str]: - return self.__overlay_color - - @overlay_color.setter - def overlay_color(self, value: ControlStateValue[str]): - self.__overlay_color = value - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # active_color - @property - def active_color(self) -> Optional[ColorValue]: - return self.__active_color - - @active_color.setter - def active_color(self, value: Optional[ColorValue]): - self.__active_color = value - self._set_enum_attr("activeColor", value, ColorEnums) - - # inactive_color - @property - def inactive_color(self) -> Optional[ColorValue]: - return self.__inactive_color - - @inactive_color.setter - def inactive_color(self, value: Optional[ColorValue]): - self.__inactive_color = value - self._set_enum_attr("inactiveColor", value, ColorEnums) - - # thumb_color - @property - def thumb_color(self) -> Optional[ColorValue]: - return self.__thumb_color - - @thumb_color.setter - def thumb_color(self, value: Optional[ColorValue]): - self.__thumb_color = value - self._set_enum_attr("thumbColor", value, ColorEnums) - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - - # on_change_start - @property - def on_change_start(self) -> OptionalControlEventCallable: - return self._get_event_handler("change_start") - - @on_change_start.setter - def on_change_start(self, handler: OptionalControlEventCallable): - self._add_event_handler("change_start", handler) - - # on_change_end - @property - def on_change_end(self) -> OptionalControlEventCallable: - return self._get_event_handler("change_end") - - @on_change_end.setter - def on_change_end(self, handler: OptionalControlEventCallable): - self._add_event_handler("change_end", handler) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) diff --git a/sdk/python/packages/flet/src/flet/core/snack_bar.py b/sdk/python/packages/flet/src/flet/core/snack_bar.py deleted file mode 100644 index c1a3cce73..000000000 --- a/sdk/python/packages/flet/src/flet/core/snack_bar.py +++ /dev/null @@ -1,321 +0,0 @@ -from enum import Enum -from typing import Any, Optional - -from flet.core.buttons import OutlinedBorder -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.types import ( - ClipBehavior, - ColorEnums, - ColorValue, - MarginValue, - OptionalControlEventCallable, - PaddingValue, -) - - -class SnackBarBehavior(Enum): - FIXED = "fixed" - FLOATING = "floating" - - -class DismissDirection(Enum): - NONE = "none" - VERTICAL = "vertical" - HORIZONTAL = "horizontal" - END_TO_START = "endToStart" - START_TO_END = "startToEnd" - UP = "up" - DOWN = "down" - - -class SnackBar(Control): - """ - A lightweight message with an optional action which briefly displays at the bottom of the screen. - - Example: - ``` - import flet as ft - - class Data: - def __init__(self) -> None: - self.counter = 0 - - d = Data() - - def main(page): - - page.snack_bar = ft.SnackBar( - content=ft.Text("Hello, world!"), - action="Alright!", - ) - page.snack_bar.open = True - - def on_click(e): - page.snack_bar = ft.SnackBar(ft.Text(f"Hello {d.counter}")) - page.snack_bar.open = True - d.counter += 1 - page.update() - - page.add(ft.ElevatedButton("Open SnackBar", on_click=on_click)) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/snackbar - """ - - def __init__( - self, - content: Control, - open: bool = False, - behavior: Optional[SnackBarBehavior] = None, - dismiss_direction: Optional[DismissDirection] = None, - show_close_icon: Optional[bool] = False, - action: Optional[str] = None, - action_color: Optional[ColorValue] = None, - close_icon_color: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, - duration: Optional[int] = None, - margin: Optional[MarginValue] = None, - padding: Optional[PaddingValue] = None, - width: OptionalNumber = None, - elevation: OptionalNumber = None, - shape: Optional[OutlinedBorder] = None, - clip_behavior: Optional[ClipBehavior] = None, - action_overflow_threshold: OptionalNumber = None, - on_action: OptionalControlEventCallable = None, - on_visible: OptionalControlEventCallable = None, - # - # Control - # - ref: Optional[Ref] = None, - disabled: Optional[bool] = None, - visible: Optional[bool] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - disabled=disabled, - visible=visible, - data=data, - ) - - self.open = open - self.behavior = behavior - self.dismiss_direction = dismiss_direction - self.show_close_icon = show_close_icon - self.close_icon_color = close_icon_color - self.margin = margin - self.padding = padding - self.width = width - self.content = content - self.action = action - self.action_color = action_color - self.bgcolor = bgcolor - self.duration = duration - self.elevation = elevation - self.on_action = on_action - self.on_visible = on_visible - self.shape = shape - self.clip_behavior = clip_behavior - self.action_overflow_threshold = action_overflow_threshold - - def _get_control_name(self): - return "snackbar" - - def _get_children(self): - self.__content._set_attr_internal("n", "content") - return [self.__content] - - def before_update(self): - super().before_update() - self._set_attr_json("shape", self.__shape) - self._set_attr_json("padding", self.__padding) - self._set_attr_json("margin", self.__margin) - - # open - @property - def open(self) -> bool: - return self._get_attr("open", data_type="bool", def_value=False) - - @open.setter - def open(self, value: Optional[bool]): - self._set_attr("open", value) - - # show_close_icon - @property - def show_close_icon(self) -> bool: - return self._get_attr("showCloseIcon", data_type="bool", def_value=False) - - @show_close_icon.setter - def show_close_icon(self, value: Optional[bool]): - self._set_attr("showCloseIcon", value) - - # content - @property - def content(self) -> Control: - return self.__content - - @content.setter - def content(self, value: Control): - self.__content = value - - # action - @property - def action(self) -> Optional[str]: - return self._get_attr("action") - - @action.setter - def action(self, value: Optional[str]): - self._set_attr("action", value) - - # action_color - @property - def action_color(self) -> Optional[ColorValue]: - return self.__action_color - - @action_color.setter - def action_color(self, value: Optional[ColorValue]): - self.__action_color = value - self._set_enum_attr("actionColor", value, ColorEnums) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgColor", value, ColorEnums) - - # close_icon_color - @property - def close_icon_color(self) -> Optional[ColorValue]: - return self.__close_icon_color - - @close_icon_color.setter - def close_icon_color(self, value: Optional[ColorValue]): - self.__close_icon_color = value - self._set_enum_attr("closeIconColor", value, ColorEnums) - - # duration - @property - def duration(self) -> Optional[int]: - return self._get_attr("duration", data_type="int") - - @duration.setter - def duration(self, value: Optional[int]): - self._set_attr("duration", value) - - # action_overflow_threshold - @property - def action_overflow_threshold(self) -> float: - return self._get_attr( - "actionOverflowThreshold", data_type="float", def_value=0.25 - ) - - @action_overflow_threshold.setter - def action_overflow_threshold(self, value: OptionalNumber): - assert ( - value is None or 0 <= value <= 1 - ), "action_overflow_threshold must be between 0 and 1 inclusive" - self._set_attr("actionOverflowThreshold", value) - - # behavior - @property - def behavior(self) -> Optional[SnackBarBehavior]: - return self.__behavior - - @behavior.setter - def behavior(self, value: Optional[SnackBarBehavior]): - self.__behavior = value - self._set_enum_attr("behavior", value, SnackBarBehavior) - - # dismissDirection - @property - def dismiss_direction(self) -> Optional[DismissDirection]: - return self.__dismiss_direction - - @dismiss_direction.setter - def dismiss_direction(self, value: Optional[DismissDirection]): - self.__dismiss_direction = value - self._set_enum_attr("dismissDirection", value, DismissDirection) - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # margin - @property - def margin(self) -> Optional[MarginValue]: - return self.__margin - - @margin.setter - def margin(self, value: Optional[MarginValue]): - self.__margin = value - - # width - @property - def width(self) -> OptionalNumber: - return self._get_attr("width", data_type="float") - - @width.setter - def width(self, value: OptionalNumber): - self._set_attr("width", value) - - # elevation - @property - def elevation(self) -> OptionalNumber: - return self._get_attr("elevation", data_type="float") - - @elevation.setter - def elevation(self, value: OptionalNumber): - assert value is None or value >= 0, "elevation cannot be negative" - self._set_attr("elevation", value) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # shape - @property - def shape(self) -> Optional[OutlinedBorder]: - return self.__shape - - @shape.setter - def shape(self, value: Optional[OutlinedBorder]): - self.__shape = value - - # on_action - @property - def on_action(self) -> OptionalControlEventCallable: - return self._get_event_handler("action") - - @on_action.setter - def on_action(self, handler: OptionalControlEventCallable): - self._add_event_handler("action", handler) - - # on_visible - @property - def on_visible(self) -> OptionalControlEventCallable: - return self._get_event_handler("visible") - - @on_visible.setter - def on_visible(self, handler: OptionalControlEventCallable): - self._add_event_handler("visible", handler) diff --git a/sdk/python/packages/flet/src/flet/core/stack.py b/sdk/python/packages/flet/src/flet/core/stack.py deleted file mode 100644 index 4c29fcdd9..000000000 --- a/sdk/python/packages/flet/src/flet/core/stack.py +++ /dev/null @@ -1,199 +0,0 @@ -from enum import Enum -from typing import Any, List, Optional, Sequence, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.alignment import Alignment -from flet.core.animation import AnimationValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.types import ( - ClipBehavior, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class StackFit(Enum): - LOOSE = "loose" - EXPAND = "expand" - PASS_THROUGH = "passThrough" - - -class Stack(ConstrainedControl, AdaptiveControl): - """ - A control that positions its children on top of each other. - - This control is useful if you want to overlap several children in a simple way, for example having some text and an image, overlaid with a gradient and a button attached to the bottom. - - Stack is also useful if you want to implement implicit animations (https://flet.dev/docs/guides/python/animations/) that require knowing absolute position of a target value. - - Example: - - ``` - import flet as ft - - def main(page: ft.Page): - st = ft.Stack( - controls=[ - ft.Image( - src=f"https://picsum.photos/300/300", - width=300, - height=300, - fit=ft.ImageFit.CONTAIN, - ), - ft.Row( - controls=[ - ft.Text( - "Image title", - color="white", - size=40, - weight="bold", - opacity=0.5, - ) - ], - alignment=ft.MainAxisAlignment.CENTER, - ), - ], - width=300, - height=300, - ) - - page.add(st) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/stack - """ - - def __init__( - self, - controls: Optional[Sequence[Control]] = None, - clip_behavior: Optional[ClipBehavior] = None, - alignment: Optional[Alignment] = None, - fit: Optional[StackFit] = None, - # - # ConstrainedControl and AdaptiveControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.__controls: List[Control] = [] - self.controls = controls - self.clip_behavior = clip_behavior - self.alignment = alignment - self.fit = fit - - def _get_control_name(self): - return "stack" - - def _get_children(self): - return self.__controls - - def before_update(self): - super().before_update() - self._set_attr_json("alignment", self.__alignment) - - def __contains__(self, item): - return item in self.__controls - - # controls - @property - def controls(self): - return self.__controls - - @controls.setter - def controls(self, value: Optional[Sequence[Control]]): - self.__controls = list(value) if value is not None else [] - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value - - # fit - @property - def fit(self) -> Optional[StackFit]: - return self.__fit - - @fit.setter - def fit(self, value: Optional[StackFit]): - self.__fit = value - self._set_enum_attr("fit", value, StackFit) diff --git a/sdk/python/packages/flet/src/flet/core/submenu_button.py b/sdk/python/packages/flet/src/flet/core/submenu_button.py deleted file mode 100644 index fdd931291..000000000 --- a/sdk/python/packages/flet/src/flet/core/submenu_button.py +++ /dev/null @@ -1,281 +0,0 @@ -import time -from typing import Any, Optional, Sequence, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.buttons import ButtonStyle -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.menu_bar import MenuStyle -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ClipBehavior, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class SubmenuButton(ConstrainedControl): - """ - A menu button that displays a cascading menu. It can be used as part of a MenuBar, or as a standalone control. - - ----- - - Online docs: https://flet.dev/docs/controls/submenubutton - """ - - def __init__( - self, - content: Optional[Control] = None, - controls: Optional[Sequence[Control]] = None, - leading: Optional[Control] = None, - trailing: Optional[Control] = None, - clip_behavior: Optional[ClipBehavior] = None, - menu_style: Optional[MenuStyle] = None, - style: Optional[ButtonStyle] = None, - alignment_offset: Optional[OffsetValue] = None, - on_open: OptionalControlEventCallable = None, - on_close: OptionalControlEventCallable = None, - on_hover: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.content = content - self.controls = controls - self.leading = leading - self.trailing = trailing - self.clip_behavior = clip_behavior - self.style = style - self.menu_style = menu_style - self.alignment_offset = alignment_offset - self.on_open = on_open - self.on_close = on_close - self.on_hover = on_hover - self.on_focus = on_focus - self.on_blur = on_blur - - def _get_control_name(self): - return "submenubutton" - - def before_update(self): - super().before_update() - if self.__style is not None: - self.__style.side = self._wrap_attr_dict(self.__style.side) - self.__style.shape = self._wrap_attr_dict(self.__style.shape) - if self.__menu_style is not None: - self.__menu_style.side = self._wrap_attr_dict(self.__menu_style.side) - self.__menu_style.shape = self._wrap_attr_dict(self.__menu_style.shape) - self._set_attr_json("style", self.__style) - self._set_attr_json("menuStyle", self.__menu_style) - - def _get_children(self): - children = [] - if self.__controls: - for c in self.__controls: - c._set_attr_internal("n", "controls") - children.append(c) - if self.__leading: - self.__leading._set_attr_internal("n", "leading") - children.append(self.__leading) - if self.__trailing: - self.__trailing._set_attr_internal("n", "trailing") - children.append(self.__trailing) - if self.__content: - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - return children - - def __contains__(self, item): - return item in self.__controls - - def focus(self): - self._set_attr_json("focus", str(time.time())) - self.update() - - # controls - @property - def controls(self): - return self.__controls - - @controls.setter - def controls(self, value: Optional[Sequence[Control]]): - self.__controls = list(value) if value is not None else [] - - # leading - @property - def leading(self) -> Optional[Control]: - return self.__leading - - @leading.setter - def leading(self, value: Optional[Control]): - self.__leading = value - - # trailing - @property - def trailing(self) -> Optional[Control]: - return self.__trailing - - @trailing.setter - def trailing(self, value: Optional[Control]): - self.__trailing = value - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # style - @property - def style(self) -> Optional[ButtonStyle]: - return self.__style - - @style.setter - def style(self, value: Optional[ButtonStyle]): - self.__style = value - - # menu_style - @property - def menu_style(self) -> Optional[MenuStyle]: - return self.__menu_style - - @menu_style.setter - def menu_style(self, value: Optional[MenuStyle]): - self.__menu_style = value - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # alignment_offset - @property - def alignment_offset(self) -> Optional[OffsetValue]: - return self.__alignment_offset - - @alignment_offset.setter - def alignment_offset(self, value: Optional[OffsetValue]): - self.__alignment_offset = value - - # on_open - @property - def on_open(self): - return self._get_event_handler("open") - - @on_open.setter - def on_open(self, handler: OptionalControlEventCallable): - self._add_event_handler("open", handler) - self._set_attr("onOpen", True if handler is not None else None) - - # on_close - @property - def on_close(self): - return self._get_event_handler("close") - - @on_close.setter - def on_close(self, handler: OptionalControlEventCallable): - self._add_event_handler("close", handler) - self._set_attr("onClose", True if handler is not None else None) - - # on_hover - @property - def on_hover(self): - return self._get_event_handler("hover") - - @on_hover.setter - def on_hover(self, handler: OptionalControlEventCallable): - self._add_event_handler("hover", handler) - self._set_attr("onHover", True if handler is not None else None) - - # on_focus - @property - def on_focus(self): - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self): - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) diff --git a/sdk/python/packages/flet/src/flet/core/switch.py b/sdk/python/packages/flet/src/flet/core/switch.py deleted file mode 100644 index ec3fbf9b0..000000000 --- a/sdk/python/packages/flet/src/flet/core/switch.py +++ /dev/null @@ -1,407 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - ControlStateValue, - IconValue, - LabelPosition, - MouseCursor, - OffsetValue, - OnFocusEvent, - OptionalControlEventCallable, - OptionalEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, -) - - -class Switch(ConstrainedControl, AdaptiveControl): - """ - A toggle represents a physical switch that allows someone to choose between two mutually exclusive options. - - or example, "On/Off", "Show/Hide". Choosing an option should produce an immediate result. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - def theme_changed(e): - page.theme_mode = ( - ft.ThemeMode.DARK - if page.theme_mode == ft.ThemeMode.LIGHT - else ft.ThemeMode.LIGHT - ) - c.label = ( - "Light theme" if page.theme_mode == ft.ThemeMode.LIGHT else "Dark theme" - ) - page.update() - - page.theme_mode = ft.ThemeMode.LIGHT - c = ft.Switch(label="Light theme", on_change=theme_changed) - page.add(c) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/switch - """ - - def __init__( - self, - label: Optional[Union[str, Control]] = None, - label_position: Optional[LabelPosition] = None, - label_style: Optional[TextStyle] = None, - value: Optional[bool] = None, - autofocus: Optional[bool] = None, - active_color: Optional[ColorValue] = None, - active_track_color: Optional[ColorValue] = None, - focus_color: Optional[ColorValue] = None, - inactive_thumb_color: Optional[ColorValue] = None, - inactive_track_color: Optional[ColorValue] = None, - thumb_color: ControlStateValue[ColorValue] = None, - thumb_icon: ControlStateValue[IconValue] = None, - track_color: ControlStateValue[ColorValue] = None, - adaptive: Optional[bool] = None, - hover_color: Optional[ColorValue] = None, - splash_radius: OptionalNumber = None, - overlay_color: ControlStateValue[ColorValue] = None, - track_outline_color: ControlStateValue[ColorValue] = None, - track_outline_width: ControlStateValue[OptionalNumber] = None, - mouse_cursor: Optional[MouseCursor] = None, - on_change: OptionalControlEventCallable = None, - on_focus: OptionalEventCallable[OnFocusEvent] = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.__on_focus = EventHandler(lambda e: OnFocusEvent(e)) - self._add_event_handler("focus", self.__on_focus.get_handler()) - - self.value = value - self.label = label - self.label_style = label_style - self.label_position = label_position - self.autofocus = autofocus - self.active_color = active_color - self.active_track_color = active_track_color - self.focus_color = focus_color - self.inactive_thumb_color = inactive_thumb_color - self.inactive_track_color = inactive_track_color - self.thumb_color = thumb_color - self.thumb_icon = thumb_icon - self.track_color = track_color - self.on_change = on_change - self.on_focus = on_focus - self.on_blur = on_blur - self.hover_color = hover_color - self.splash_radius = splash_radius - self.overlay_color = overlay_color - self.track_outline_color = track_outline_color - self.track_outline_width = track_outline_width - self.mouse_cursor = mouse_cursor - - def _get_control_name(self): - return "switch" - - def before_update(self): - super().before_update() - self._set_attr_json("thumbColor", self.__thumb_color, wrap_attr_dict=True) - self._set_attr_json("overlayColor", self.__overlay_color, wrap_attr_dict=True) - self._set_attr_json( - "trackOutlineColor", self.__track_outline_color, wrap_attr_dict=True - ) - self._set_attr_json( - "trackOutlineWidth", self.__track_outline_width, wrap_attr_dict=True - ) - self._set_attr_json("thumbIcon", self.__thumb_icon, wrap_attr_dict=True) - self._set_attr_json("trackColor", self.__track_color, wrap_attr_dict=True) - self._set_attr_json("labelStyle", self.__label_style) - - def _get_children(self): - return [self.__label] if isinstance(self.__label, Control) else [] - - # value - @property - def value(self) -> bool: - return self._get_attr("value", data_type="bool", def_value=False) - - @value.setter - def value(self, value: Optional[bool]): - self._set_attr("value", value) - - # label - @property - def label(self) -> Optional[Union[str, Control]]: - return self.__label - - @label.setter - def label(self, value: Optional[Union[str, Control]]): - self.__label = value - if not isinstance(value, Control): - self._set_attr("label", value) - - # hover_color - @property - def hover_color(self) -> Optional[ColorValue]: - return self.__hover_color - - @hover_color.setter - def hover_color(self, value: Optional[ColorValue]): - self.__hover_color = value - self._set_enum_attr("hoverColor", value, ColorEnums) - - # track_outline_color - @property - def track_outline_color(self) -> ControlStateValue[ColorValue]: - return self.__track_outline_color - - @track_outline_color.setter - def track_outline_color(self, value: ControlStateValue[ColorValue]): - self.__track_outline_color = value - - # track_outline_width - @property - def track_outline_width(self) -> ControlStateValue[OptionalNumber]: - return self.__track_outline_width - - @track_outline_width.setter - def track_outline_width(self, value: ControlStateValue[OptionalNumber]): - self.__track_outline_width = value - - # overlay_color - @property - def overlay_color(self) -> ControlStateValue[str]: - return self.__overlay_color - - @overlay_color.setter - def overlay_color(self, value: ControlStateValue[str]): - self.__overlay_color = value - - # splash_radius - @property - def splash_radius(self) -> OptionalNumber: - return self._get_attr("splashRadius", data_type="float") - - @splash_radius.setter - def splash_radius(self, value: OptionalNumber): - assert value is None or value >= 0, "splash_radius cannot be negative" - self._set_attr("splashRadius", value) - - # label_style - @property - def label_style(self) -> Optional[TextStyle]: - return self.__label_style - - @label_style.setter - def label_style(self, value: Optional[TextStyle]): - self.__label_style = value - - # label_position - @property - def label_position(self) -> Optional[LabelPosition]: - return self.__label_position - - @label_position.setter - def label_position(self, value: Optional[LabelPosition]): - self.__label_position = value - self._set_enum_attr("labelPosition", value, LabelPosition) - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self.__mouse_cursor - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self.__mouse_cursor = value - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # active_color - @property - def active_color(self) -> Optional[ColorValue]: - return self.__active_color - - @active_color.setter - def active_color(self, value: Optional[ColorValue]): - self.__active_color = value - self._set_enum_attr("activeColor", value, ColorEnums) - - # active_track_color - @property - def active_track_color(self) -> Optional[ColorValue]: - return self.__active_track_color - - @active_track_color.setter - def active_track_color(self, value: Optional[ColorValue]): - self.__active_track_color = value - self._set_enum_attr("activeTrackColor", value, ColorEnums) - - # focus_color - @property - def focus_color(self) -> Optional[ColorValue]: - return self.__focus_color - - @focus_color.setter - def focus_color(self, value: Optional[ColorValue]): - self.__focus_color = value - self._set_enum_attr("focusColor", value, ColorEnums) - - # inactive_thumb_color - @property - def inactive_thumb_color(self) -> Optional[ColorValue]: - return self.__inactive_thumb_color - - @inactive_thumb_color.setter - def inactive_thumb_color(self, value: Optional[ColorValue]): - self.__inactive_thumb_color = value - self._set_enum_attr("inactiveThumbColor", value, ColorEnums) - - # inactive_track_color - @property - def inactive_track_color(self) -> Optional[ColorValue]: - return self.__inactive_track_color - - @inactive_track_color.setter - def inactive_track_color(self, value: Optional[ColorValue]): - self.__inactive_track_color = value - self._set_enum_attr("inactiveTrackColor", value, ColorEnums) - - # thumb_color - @property - def thumb_color(self) -> ControlStateValue[str]: - return self.__thumb_color - - @thumb_color.setter - def thumb_color(self, value: ControlStateValue[str]): - self.__thumb_color = value - - # thumb_icon - @property - def thumb_icon(self) -> ControlStateValue[IconValue]: - return self.__thumb_icon - - @thumb_icon.setter - def thumb_icon(self, value: ControlStateValue[IconValue]): - self.__thumb_icon = value - - # track_color - @property - def track_color(self) -> ControlStateValue[str]: - return self.__track_color - - @track_color.setter - def track_color(self, value: ControlStateValue[str]): - self.__track_color = value - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - - # on_focus - @property - def on_focus(self) -> OptionalEventCallable[OnFocusEvent]: - return self.__on_focus.handler - - @on_focus.setter - def on_focus(self, handler: OptionalEventCallable[OnFocusEvent]): - self.__on_focus.handler = handler - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) diff --git a/sdk/python/packages/flet/src/flet/core/tabs.py b/sdk/python/packages/flet/src/flet/core/tabs.py deleted file mode 100644 index 42faa6a7b..000000000 --- a/sdk/python/packages/flet/src/flet/core/tabs.py +++ /dev/null @@ -1,570 +0,0 @@ -from typing import Any, List, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.border import BorderSide -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.form_field_control import IconValueOrControl -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.types import ( - BorderRadiusValue, - ClipBehavior, - ColorEnums, - ColorValue, - ControlStateValue, - IconEnums, - IconValue, - MarginValue, - MouseCursor, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, - TabAlignment, -) - - -class Tab(AdaptiveControl): - def __init__( - self, - text: Optional[str] = None, - content: Optional[Control] = None, - tab_content: Optional[Control] = None, - icon: Optional[IconValueOrControl] = None, - height: OptionalNumber = None, - icon_margin: Optional[MarginValue] = None, - # - # Control and AdaptiveControl - # - ref: Optional[Ref] = None, - visible: Optional[bool] = None, - adaptive: Optional[bool] = None, - ): - Control.__init__(self, ref=ref, visible=visible) - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.text = text - self.icon = icon - self.content = content - self.tab_content = tab_content - self.height = height - self.icon_margin = icon_margin - - def _get_control_name(self): - return "tab" - - def _get_children(self): - children = [] - if self.__tab_content: - self.__tab_content._set_attr_internal("n", "tab_content") - children.append(self.__tab_content) - if self.__content: - self.__content._set_attr_internal("n", "content") - children.append(self.__content) - if isinstance(self.__icon, Control): - self.__icon._set_attr_internal("n", "icon") - children.append(self.__icon) - return children - - def before_update(self): - super().before_update() - self._set_attr_json("iconMargin", self.__icon_margin) - if isinstance(self.__icon, IconValue): - self._set_enum_attr("icon", self.__icon, IconEnums) - - # text - @property - def text(self) -> Optional[str]: - return self._get_attr("text") - - @text.setter - def text(self, value: Optional[str]): - self._set_attr("text", value) - - # height - @property - def height(self) -> Optional[float]: - return self._get_attr("height", data_type="float") - - @height.setter - def height(self, value: OptionalNumber): - self._set_attr("height", value) - - # icon - @property - def icon(self) -> Optional[IconValueOrControl]: - return self.__icon - - @icon.setter - def icon(self, value: Optional[IconValueOrControl]): - self.__icon = value - - # tab_content - @property - def tab_content(self) -> Optional[Control]: - return self.__tab_content - - @tab_content.setter - def tab_content(self, value: Optional[Control]): - self.__tab_content = value - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # icon_margin - @property - def icon_margin(self) -> Optional[MarginValue]: - return self.__icon_margin - - @icon_margin.setter - def icon_margin(self, value: Optional[MarginValue]): - self.__icon_margin = value - - -class Tabs(ConstrainedControl, AdaptiveControl): - """ - The Tabs control is used for navigating frequently accessed, distinct content categories. Tabs allow for navigation between two or more content views and relies on text headers to articulate the different sections of content. - - Example: - ``` - import flet as ft - - - def main(page: ft.Page): - - t = ft.Tabs( - selected_index=1, - animation_duration=300, - tabs=[ - ft.Tab( - text="Tab 1", - content=ft.Container( - content=ft.Text("This is Tab 1"), alignment=ft.alignment.center - ), - ), - ft.Tab( - tab_content=ft.Icon(ft.icons.SEARCH), - content=ft.Text("This is Tab 2"), - ), - ft.Tab( - text="Tab 3", - icon=ft.icons.SETTINGS, - content=ft.Text("This is Tab 3"), - ), - ], - expand=1, - ) - - page.add(t) - - - ft.app(target=main) - - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/tabs - """ - - def __init__( - self, - tabs: Optional[List[Tab]] = None, - selected_index: Optional[int] = None, - scrollable: Optional[bool] = None, - tab_alignment: Optional[TabAlignment] = None, - animation_duration: Optional[int] = None, - divider_color: Optional[ColorValue] = None, - indicator_color: Optional[ColorValue] = None, - indicator_border_radius: Optional[BorderRadiusValue] = None, - indicator_border_side: Optional[BorderSide] = None, - indicator_padding: Optional[PaddingValue] = None, - indicator_tab_size: Optional[bool] = None, - is_secondary: Optional[bool] = None, - label_color: Optional[ColorValue] = None, - label_padding: Optional[PaddingValue] = None, - label_text_style: Optional[TextStyle] = None, - unselected_label_color: Optional[ColorValue] = None, - unselected_label_text_style: Optional[TextStyle] = None, - overlay_color: ControlStateValue[ColorValue] = None, - divider_height: OptionalNumber = None, - indicator_thickness: OptionalNumber = None, - enable_feedback: Optional[str] = None, - mouse_cursor: Optional[MouseCursor] = None, - padding: Optional[PaddingValue] = None, - splash_border_radius: Optional[BorderRadiusValue] = None, - clip_behavior: Optional[ClipBehavior] = None, - on_click: OptionalControlEventCallable = None, - on_change: OptionalControlEventCallable = None, - # - # ConstrainedControl and AdaptiveControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - visible=visible, - disabled=disabled, - data=data, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.tabs = tabs - self.selected_index = selected_index - self.scrollable = scrollable - self.tab_alignment = tab_alignment - self.animation_duration = animation_duration - self.divider_color = divider_color - self.label_color = label_color - self.unselected_label_color = unselected_label_color - self.indicator_color = indicator_color - self.indicator_border_radius = indicator_border_radius - self.indicator_border_side = indicator_border_side - self.indicator_padding = indicator_padding - self.indicator_tab_size = indicator_tab_size - self.overlay_color = overlay_color - self.on_change = on_change - self.divider_height = divider_height - self.indicator_thickness = indicator_thickness - self.enable_feedback = enable_feedback - self.is_secondary = is_secondary - self.mouse_cursor = mouse_cursor - self.clip_behavior = clip_behavior - self.label_padding = label_padding - self.label_text_style = label_text_style - self.unselected_label_text_style = unselected_label_text_style - self.padding = padding - self.splash_border_radius = splash_border_radius - self.on_click = on_click - - def _get_control_name(self): - return "tabs" - - def before_update(self): - super().before_update() - self._set_attr_json("overlayColor", self.__overlay_color, wrap_attr_dict=True) - self._set_attr_json("indicatorBorderRadius", self.__indicator_border_radius) - self._set_attr_json("indicatorBorderSide", self.__indicator_border_side) - self._set_attr_json("indicatorPadding", self.__indicator_padding) - self._set_attr_json("labelPadding", self.__label_padding) - self._set_attr_json("labelTextStyle", self.__label_text_style) - self._set_attr_json( - "unselectedLabelTextStyle", self.__unselected_label_text_style - ) - - def _get_children(self): - return self.__tabs - - def __contains__(self, item): - return item in self.__tabs - - # tabs - @property - def tabs(self) -> List[Tab]: - return self.__tabs - - @tabs.setter - def tabs(self, value: Optional[List[Tab]]): - self.__tabs = value if value is not None else [] - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - - # selected_index - @property - def selected_index(self) -> int: - return self._get_attr("selectedIndex", data_type="int", def_value=0) - - @selected_index.setter - def selected_index(self, value: Optional[int]): - self._set_attr("selectedIndex", value) - - # scrollable - @property - def scrollable(self) -> bool: - return self._get_attr("scrollable", data_type="bool", def_value=True) - - @scrollable.setter - def scrollable(self, value: Optional[bool]): - self._set_attr("scrollable", value) - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self.__mouse_cursor - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self.__mouse_cursor = value - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # is_secondary - @property - def is_secondary(self) -> bool: - return self._get_attr("isSecondary", data_type="bool", def_value=False) - - @is_secondary.setter - def is_secondary(self, value: Optional[bool]): - self._set_attr("isSecondary", value) - - # tab_alignment - @property - def tab_alignment(self) -> Optional[TabAlignment]: - return self.__tab_alignment - - @tab_alignment.setter - def tab_alignment(self, value: Optional[TabAlignment]): - self.__tab_alignment = value - self._set_enum_attr("tabAlignment", value, TabAlignment) - - # animation_duration - @property - def animation_duration(self) -> Optional[int]: - return self._get_attr("animationDuration", data_type="int") - - @animation_duration.setter - def animation_duration(self, value: Optional[int]): - self._set_attr("animationDuration", value) - - # divider_height - @property - def divider_height(self) -> float: - return self._get_attr("dividerHeight", data_type="float", def_value=1.0) - - @divider_height.setter - def divider_height(self, value: OptionalNumber): - self._set_attr("dividerHeight", value) - - # enable_feedback - @property - def enable_feedback(self): - return self._get_attr("enableFeedback") - - @enable_feedback.setter - def enable_feedback(self, value: Optional[bool]): - self._set_attr("enableFeedback", value) - - # indicator_thickness - @property - def indicator_thickness(self) -> float: - return self._get_attr("indicatorThickness", data_type="float", def_value=2.0) - - @indicator_thickness.setter - def indicator_thickness(self, value: OptionalNumber): - assert value is None or value > 0, "indicator_thickness must be greater than 0" - self._set_attr("indicatorThickness", value) - - # divider_color - @property - def divider_color(self) -> Optional[ColorValue]: - return self.__divider_color - - @divider_color.setter - def divider_color(self, value: Optional[ColorValue]): - self.__divider_color = value - self._set_enum_attr("dividerColor", value, ColorEnums) - - # indicator_color - @property - def indicator_color(self) -> Optional[ColorValue]: - return self.__indicator_color - - @indicator_color.setter - def indicator_color(self, value: Optional[ColorValue]): - self.__indicator_color = value - self._set_enum_attr("indicatorColor", value, ColorEnums) - - # indicator_border_radius - @property - def indicator_border_radius(self) -> Optional[BorderRadiusValue]: - return self.__indicator_border_radius - - @indicator_border_radius.setter - def indicator_border_radius(self, value: Optional[BorderRadiusValue]): - self.__indicator_border_radius = value - - # indicator_border_side - @property - def indicator_border_side(self) -> Optional[BorderSide]: - return self.__indicator_border_side - - @indicator_border_side.setter - def indicator_border_side(self, value: Optional[BorderSide]): - self.__indicator_border_side = value - - # indicator_padding - @property - def indicator_padding(self) -> Optional[PaddingValue]: - return self.__indicator_padding - - @indicator_padding.setter - def indicator_padding(self, value: Optional[PaddingValue]): - self.__indicator_padding = value - - # indicator_tab_size - @property - def indicator_tab_size(self) -> bool: - return self._get_attr("indicatorTabSize", data_type="bool", def_value=False) - - @indicator_tab_size.setter - def indicator_tab_size(self, value: Optional[bool]): - self._set_attr("indicatorTabSize", value) - - # label_color - @property - def label_color(self) -> Optional[ColorValue]: - return self.__label_color - - @label_color.setter - def label_color(self, value: Optional[ColorValue]): - self.__label_color = value - self._set_enum_attr("labelColor", value, ColorEnums) - - # unselected_label_color - @property - def unselected_label_color(self) -> Optional[ColorValue]: - return self.__unselected_label_color - - @unselected_label_color.setter - def unselected_label_color(self, value: Optional[ColorValue]): - self.__unselected_label_color = value - self._set_enum_attr("unselectedLabelColor", value, ColorEnums) - - # overlay_color - @property - def overlay_color(self) -> ControlStateValue[ColorValue]: - return self.__overlay_color - - @overlay_color.setter - def overlay_color(self, value: ControlStateValue[ColorValue]): - self.__overlay_color = value - - # label_padding - @property - def label_padding(self) -> Optional[PaddingValue]: - return self.__label_padding - - @label_padding.setter - def label_padding(self, value: Optional[PaddingValue]): - self.__label_padding = value - - # label_text_style - @property - def label_text_style(self) -> Optional[TextStyle]: - return self.__label_text_style - - @label_text_style.setter - def label_text_style(self, value: Optional[TextStyle]): - self.__label_text_style = value - - # unselected_label_text_style - @property - def unselected_label_text_style(self) -> Optional[TextStyle]: - return self.__unselected_label_text_style - - @unselected_label_text_style.setter - def unselected_label_text_style(self, value: Optional[TextStyle]): - self.__unselected_label_text_style = value - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # splash_border_radius - @property - def splash_border_radius(self) -> Optional[BorderRadiusValue]: - return self.__splash_border_radius - - @splash_border_radius.setter - def splash_border_radius(self, value: Optional[BorderRadiusValue]): - self.__splash_border_radius = value - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) diff --git a/sdk/python/packages/flet/src/flet/core/text.py b/sdk/python/packages/flet/src/flet/core/text.py deleted file mode 100644 index 99ff41b72..000000000 --- a/sdk/python/packages/flet/src/flet/core/text.py +++ /dev/null @@ -1,477 +0,0 @@ -import json -from dataclasses import dataclass -from enum import Enum -from typing import Any, List, Optional, Union -from warnings import warn - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.text_span import TextSpan -from flet.core.text_style import TextOverflow, TextStyle, TextThemeStyle -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - FontWeight, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, - TextAlign, -) - -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal - - -class TextAffinity(Enum): - UPSTREAM = "upstream" - DOWNSTREAM = "downstream" - - -@dataclass -class TextSelection: - start: Optional[int] = None - end: Optional[int] = None - selection: Optional[str] = None - base_offset: Optional[int] = None - extent_offset: Optional[int] = None - affinity: Optional[TextAffinity] = None - directional: Optional[bool] = None - collapsed: Optional[bool] = None - valid: Optional[bool] = None - normalized: Optional[bool] = None - - -class TextSelectionChangeCause(Enum): - UNKNOWN = "unknown" - TAP = "tap" - DOUBLE_TAP = "doubleTap" - LONG_PRESS = "longPress" - FORCE_PRESS = "forcePress" - KEYBOARD = "keyboard" - TOOLBAR = "toolbar" - DRAG = "drag" - SCRIBBLE = "scribble" - - -class TextSelectionChangeEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.text: str = d.get("text") - self.cause = TextSelectionChangeCause(d.get("cause")) - start = d.get("start") - end = d.get("end") - self.selection = TextSelection( - start=start, - end=end, - selection=self.text[start:end] if (start != -1 and end != -1) else "", - base_offset=d.get("base_offset"), - extent_offset=d.get("extent_offset"), - affinity=d.get("affinity"), - directional=d.get("directional"), - collapsed=d.get("collapsed"), - valid=d.get("valid"), - normalized=d.get("normalized"), - ) - - -class Text(ConstrainedControl): - """ - Text is a control for displaying text. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.title = "Text examples" - - page.add( - ft.Text("Size 10", size=10), - ft.Text("Size 30, Italic", size=20, color="pink600", italic=True), - ft.Text("Limit long text to 2 lines and fading", style=ft.TextThemeStyle.HEADLINE_SMALL), - ft.Text( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur quis nibh vitae purus consectetur facilisis sed vitae ipsum. Quisque faucibus sed nulla placerat sagittis. Phasellus condimentum risus vitae nulla vestibulum auctor. Curabitur scelerisque, nibh eget imperdiet consequat, odio ante tempus diam, sed volutpat nisl erat eget turpis. Sed viverra, diam sit amet blandit vulputate, mi tellus dapibus lorem, vitae vehicula diam mauris placerat diam. Morbi sit amet pretium turpis, et consequat ligula. Nulla velit sem, suscipit sit amet dictum non, tincidunt sed nulla. Aenean pellentesque odio porttitor sagittis aliquam. Nam varius at metus vitae vulputate. Praesent faucibus nibh lorem, eu pretium dolor dictum nec. Phasellus eget dui laoreet, viverra magna vitae, pellentesque diam.", - max_lines=2, - ), - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/text - """ - - def __init__( - self, - value: Optional[str] = None, - spans: Optional[List[TextSpan]] = None, - text_align: Optional[TextAlign] = None, - font_family: Optional[str] = None, - size: OptionalNumber = None, - weight: Optional[FontWeight] = None, - italic: Optional[bool] = None, - style: Union[TextThemeStyle, TextStyle, None] = None, - theme_style: Optional[TextThemeStyle] = None, - max_lines: Optional[int] = None, - overflow: Optional[TextOverflow] = None, - selectable: Optional[bool] = None, - no_wrap: Optional[bool] = None, - color: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, - semantics_label: Optional[str] = None, - show_selection_cursor: Optional[bool] = None, - enable_interactive_selection: Optional[bool] = None, - selection_cursor_width: OptionalNumber = None, - selection_cursor_height: OptionalNumber = None, - selection_cursor_color: Optional[ColorValue] = None, - on_tap: OptionalControlEventCallable = None, - on_selection_change: OptionalEventCallable[TextSelectionChangeEvent] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - rtl=rtl, - ) - - self.__on_selection_change = EventHandler(lambda e: TextSelectionChangeEvent(e)) - - self._add_event_handler( - "selection_change", self.__on_selection_change.get_handler() - ) - self.value = value - self.spans = spans - self.text_align = text_align - self.font_family = font_family - self.size = size - self.weight = weight - self.italic = italic - self.no_wrap = no_wrap - self.style = style - self.theme_style = theme_style - self.max_lines = max_lines - self.overflow = overflow - self.selectable = selectable - self.color = color - self.bgcolor = bgcolor - self.semantics_label = semantics_label - self.on_tap = on_tap - self.on_selection_change = on_selection_change - self.show_selection_cursor = show_selection_cursor - self.enable_interactive_selection = enable_interactive_selection - self.selection_cursor_width = selection_cursor_width - self.selection_cursor_height = selection_cursor_height - self.selection_cursor_color = selection_cursor_color - - def _get_control_name(self): - return "text" - - def _get_children(self): - return self.__spans - - def before_update(self): - super().before_update() - if isinstance(self.__style, TextStyle): - self._set_attr_json("style", self.__style) - - # value - @property - def value(self) -> Optional[str]: - return self._get_attr("value") - - @value.setter - def value(self, value: Optional[str]): - self._set_attr("value", value) - - # spans - @property - def spans(self) -> List[TextSpan]: - return self.__spans - - @spans.setter - def spans(self, value: Optional[List[TextSpan]]): - self.__spans = value if value is not None else [] - - # text_align - @property - def text_align(self) -> Optional[TextAlign]: - return self.__text_align - - @text_align.setter - def text_align(self, value: Optional[TextAlign]): - self.__text_align = value - self._set_enum_attr("textAlign", value, TextAlign) - - # font_family - @property - def font_family(self) -> Optional[str]: - return self._get_attr("fontFamily") - - @font_family.setter - def font_family(self, value: Optional[str]): - self._set_attr("fontFamily", value) - - # size - @property - def size(self) -> OptionalNumber: - return self._get_attr("size", data_type="float") - - @size.setter - def size(self, value: OptionalNumber): - self._set_attr("size", value) - - # weight - @property - def weight(self) -> Optional[FontWeight]: - return self.__weight - - @weight.setter - def weight(self, value: Optional[FontWeight]): - self.__weight = value - self._set_enum_attr("weight", value, FontWeight) - - # style - @property - def style(self) -> Union[TextThemeStyle, TextStyle, None]: - return self.__style - - @style.setter - def style(self, value: Union[TextThemeStyle, TextStyle, None]): - self.__style = value - if isinstance(value, (TextThemeStyle, str)) or value is None: - self._set_attr( - "style", value.value if isinstance(value, TextThemeStyle) else value - ) - if value is not None: - warn( - "If you wish to set the TextThemeStyle, use `Text.theme_style` instead. " - "The `Text.style` property should be used to set the TextStyle only.", - stacklevel=2, - category=DeprecationWarning, - ) - - # theme_style - @property - def theme_style(self) -> Optional[TextThemeStyle]: - return self.__theme_style - - @theme_style.setter - def theme_style(self, value: Optional[TextThemeStyle]): - self.__theme_style = value - self._set_enum_attr("theme_style", value, TextThemeStyle) - - # italic - @property - def italic(self) -> bool: - return self._get_attr("italic", data_type="bool", def_value=False) - - @italic.setter - def italic(self, value: Optional[bool]): - self._set_attr("italic", value) - - # no_wrap - @property - def no_wrap(self) -> bool: - return self._get_attr("italic", data_type="noWrap", def_value=False) - - @no_wrap.setter - def no_wrap(self, value: Optional[bool]): - self._set_attr("noWrap", value) - - # selectable - @property - def selectable(self) -> bool: - return self._get_attr("selectable", data_type="bool", def_value=False) - - @selectable.setter - def selectable(self, value: Optional[bool]): - self._set_attr("selectable", value) - - # max_lines - @property - def max_lines(self) -> Optional[int]: - return self._get_attr("maxLines") - - @max_lines.setter - def max_lines(self, value: Optional[int]): - self._set_attr("maxLines", value) - - # overflow - @property - def overflow(self) -> Optional[TextOverflow]: - return self.__overflow - - @overflow.setter - def overflow(self, value: Optional[TextOverflow]): - self.__overflow = value - self._set_enum_attr("overflow", value, TextOverflow) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # semantics_label - @property - def semantics_label(self) -> Optional[str]: - return self._get_attr("semanticsLabel") - - @semantics_label.setter - def semantics_label(self, value: Optional[str]): - self._set_attr("semanticsLabel", value) - - # selection_cursor_color - @property - def selection_cursor_color(self) -> Optional[str]: - return self._get_attr("selectionCursorColor") - - @selection_cursor_color.setter - def selection_cursor_color(self, value: Optional[str]): - self._set_attr("selectionCursorColor", value) - - # selection_cursor_height - @property - def selection_cursor_height(self) -> OptionalNumber: - return self._get_attr("selectionCursorHeight", data_type="float") - - @selection_cursor_height.setter - def selection_cursor_height(self, value: OptionalNumber): - self._set_attr("selectionCursorHeight", value) - - # selection_cursor_width - @property - def selection_cursor_width(self) -> OptionalNumber: - return self._get_attr("selectionCursorWidth", data_type="float", def_value=2.0) - - @selection_cursor_width.setter - def selection_cursor_width(self, value: OptionalNumber): - self._set_attr("selectionCursorWidth", value) - - # show_selection_cursor - @property - def show_selection_cursor(self) -> Optional[bool]: - return self._get_attr("showSelectionCursor", data_type="bool", def_value=False) - - @show_selection_cursor.setter - def show_selection_cursor(self, value: Optional[bool]): - self._set_attr("showSelectionCursor", value) - - # enable_interactive_selection - @property - def enable_interactive_selection(self) -> Optional[bool]: - return self._get_attr( - "enableInteractiveSelection", data_type="bool", def_value=True - ) - - @enable_interactive_selection.setter - def enable_interactive_selection(self, value: Optional[bool]): - self._set_attr("enableInteractiveSelection", value) - - # on_tap - @property - def on_tap(self) -> OptionalControlEventCallable: - return self._get_event_handler("tap") - - @on_tap.setter - def on_tap(self, handler: OptionalControlEventCallable): - self._add_event_handler("tap", handler) - - # on_selection_change - @property - def on_selection_change( - self, - ) -> OptionalEventCallable[TextSelectionChangeEvent]: - return self.__on_selection_change.handler - - @on_selection_change.setter - def on_selection_change( - self, handler: OptionalEventCallable[TextSelectionChangeEvent] - ): - self.__on_selection_change.handler = handler diff --git a/sdk/python/packages/flet/src/flet/core/text_button.py b/sdk/python/packages/flet/src/flet/core/text_button.py deleted file mode 100644 index 138f627e3..000000000 --- a/sdk/python/packages/flet/src/flet/core/text_button.py +++ /dev/null @@ -1,298 +0,0 @@ -import time -from typing import Any, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.buttons import ButtonStyle -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ClipBehavior, - ColorEnums, - ColorValue, - IconEnums, - IconValue, - OffsetValue, - OptionalControlEventCallable, - ResponsiveNumber, - RotateValue, - ScaleValue, - UrlTarget, -) - - -class TextButton(ConstrainedControl, AdaptiveControl): - """ - Text buttons are used for the lowest priority actions, especially when presenting multiple options. Text buttons can be placed on a variety of backgrounds. Until the button is interacted with, its container isn’t visible. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.title = "Basic text buttons" - page.add( - ft.TextButton(text="Text button"), - ft.TextButton("Disabled button", disabled=True), - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/textbutton - """ - - def __init__( - self, - text: Optional[str] = None, - icon: Optional[IconValue] = None, - icon_color: Optional[ColorValue] = None, - content: Optional[Control] = None, - style: Optional[ButtonStyle] = None, - autofocus: Optional[bool] = None, - url: Optional[str] = None, - url_target: Optional[UrlTarget] = None, - clip_behavior: Optional[ClipBehavior] = None, - on_click: OptionalControlEventCallable = None, - on_long_press: OptionalControlEventCallable = None, - on_hover: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - # - # ConstrainedControl and AdaptiveControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - adaptive: Optional[bool] = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.text = text - self.icon = icon - self.icon_color = icon_color - self.style = style - self.content = content - self.autofocus = autofocus - self.url = url - self.url_target = url_target - self.clip_behavior = clip_behavior - self.on_click = on_click - self.on_long_press = on_long_press - self.on_hover = on_hover - self.on_focus = on_focus - self.on_blur = on_blur - - def _get_control_name(self): - return "textbutton" - - def before_update(self): - super().before_update() - if self.__style is not None: - self.__style.side = self._wrap_attr_dict(self.__style.side) - self.__style.shape = self._wrap_attr_dict(self.__style.shape) - self.__style.padding = self._wrap_attr_dict(self.__style.padding) - self._set_attr_json("style", self.__style) - - def _get_children(self): - if self.__content is None: - return [] - self.__content._set_attr_internal("n", "content") - return [self.__content] - - def focus(self): - self._set_attr_json("focus", str(time.time())) - self.update() - - # text - @property - def text(self) -> Optional[str]: - return self._get_attr("text") - - @text.setter - def text(self, value: Optional[str]): - self._set_attr("text", value) - - # icon - @property - def icon(self) -> Optional[IconValue]: - return self.__icon - - @icon.setter - def icon(self, value: Optional[IconValue]): - self.__icon = value - self._set_enum_attr("icon", value, IconEnums) - - # icon_color - @property - def icon_color(self) -> Optional[ColorValue]: - return self.__icon_color - - @icon_color.setter - def icon_color(self, value: Optional[ColorValue]): - self.__icon_color = value - self._set_enum_attr("iconColor", value, ColorEnums) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # style - @property - def style(self) -> Optional[ButtonStyle]: - return self.__style - - @style.setter - def style(self, value: Optional[ButtonStyle]): - self.__style = value - - # url - @property - def url(self) -> Optional[str]: - return self._get_attr("url") - - @url.setter - def url(self, value: Optional[str]): - self._set_attr("url", value) - - # url_target - @property - def url_target(self) -> Optional[UrlTarget]: - return self.__url_target - - @url_target.setter - def url_target(self, value: Optional[UrlTarget]): - self.__url_target = value - self._set_enum_attr("urlTarget", value, UrlTarget) - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) - - # on_long_press - @property - def on_long_press(self) -> OptionalControlEventCallable: - return self._get_event_handler("long_press") - - @on_long_press.setter - def on_long_press(self, handler: OptionalControlEventCallable): - self._add_event_handler("long_press", handler) - self._set_attr("onLongPress", True if handler is not None else None) - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # on_hover - @property - def on_hover(self) -> OptionalControlEventCallable: - return self._get_event_handler("hover") - - @on_hover.setter - def on_hover(self, handler: OptionalControlEventCallable): - self._add_event_handler("hover", handler) - self._set_attr("onHover", True if handler is not None else None) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) diff --git a/sdk/python/packages/flet/src/flet/core/text_span.py b/sdk/python/packages/flet/src/flet/core/text_span.py deleted file mode 100644 index b01333ff9..000000000 --- a/sdk/python/packages/flet/src/flet/core/text_span.py +++ /dev/null @@ -1,146 +0,0 @@ -from typing import Any, List, Optional - -from flet.core.inline_span import InlineSpan -from flet.core.text_style import TextStyle -from flet.core.types import OptionalControlEventCallable, UrlTarget - - -class TextSpan(InlineSpan): - def __init__( - self, - text: Optional[str] = None, - style: Optional[TextStyle] = None, - spans: Optional[List[InlineSpan]] = None, - url: Optional[str] = None, - url_target: Optional[UrlTarget] = None, - semantics_label: Optional[str] = None, - spell_out: Optional[bool] = None, - on_click: OptionalControlEventCallable = None, - on_enter: OptionalControlEventCallable = None, - on_exit: OptionalControlEventCallable = None, - # - # Control - # - ref=None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - InlineSpan.__init__( - self, ref=ref, visible=visible, disabled=disabled, data=data - ) - - self.text = text - self.style = style - self.spans = spans - self.url = url - self.url_target = url_target - self.semantics_label = semantics_label - self.spell_out = spell_out - self.on_click = on_click - self.on_enter = on_enter - self.on_exit = on_exit - - def _get_control_name(self): - return "textspan" - - def _get_children(self): - return self.__spans - - def before_update(self): - super().before_update() - self._set_attr_json("style", self.__style) - - # text - @property - def text(self) -> Optional[str]: - return self._get_attr("text") - - @text.setter - def text(self, value: Optional[str]): - self._set_attr("text", value) - - # semantics_label - @property - def semantics_label(self) -> Optional[str]: - return self._get_attr("semanticsLabel") - - @semantics_label.setter - def semantics_label(self, value: Optional[str]): - self._set_attr("semanticsLabel", value) - - # spell_out - @property - def spell_out(self) -> Optional[bool]: - return self._get_attr("spellOut") - - @spell_out.setter - def spell_out(self, value: Optional[bool]): - self._set_attr("spellOut", value) - - # style - @property - def style(self) -> Optional[TextStyle]: - return self.__style - - @style.setter - def style(self, value: Optional[TextStyle]): - self.__style = value - - # spans - @property - def spans(self) -> List[InlineSpan]: - return self.__spans - - @spans.setter - def spans(self, value: Optional[List[InlineSpan]]): - self.__spans = value if value is not None else [] - - # url - @property - def url(self) -> Optional[str]: - return self._get_attr("url") - - @url.setter - def url(self, value: Optional[str]): - self._set_attr("url", value) - - # url_target - @property - def url_target(self) -> Optional[UrlTarget]: - return self.__url_target - - @url_target.setter - def url_target(self, value: Optional[UrlTarget]): - self.__url_target = value - self._set_enum_attr("urlTarget", value, UrlTarget) - - # on_click - @property - def on_click(self) -> OptionalControlEventCallable: - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) - self._set_attr("onClick", True if handler is not None else None) - - # on_enter - @property - def on_enter(self) -> OptionalControlEventCallable: - return self._get_event_handler("enter") - - @on_enter.setter - def on_enter(self, handler: OptionalControlEventCallable): - self._add_event_handler("enter", handler) - self._set_attr("onEnter", True if handler is not None else None) - - # on_exit - @property - def on_exit(self) -> OptionalControlEventCallable: - return self._get_event_handler("exit") - - @on_exit.setter - def on_exit(self, handler: OptionalControlEventCallable): - self._add_event_handler("exit", handler) - self._set_attr("onExit", True if handler is not None else None) diff --git a/sdk/python/packages/flet/src/flet/core/text_style.py b/sdk/python/packages/flet/src/flet/core/text_style.py deleted file mode 100644 index 0e309af80..000000000 --- a/sdk/python/packages/flet/src/flet/core/text_style.py +++ /dev/null @@ -1,84 +0,0 @@ -from dataclasses import dataclass -from enum import Enum, IntFlag -from typing import List, Optional, Union - -from flet.core.box import BoxShadow -from flet.core.painting import Paint -from flet.core.types import ColorValue, FontWeight, OptionalNumber - - -class TextOverflow(Enum): - CLIP = "clip" - ELLIPSIS = "ellipsis" - FADE = "fade" - VISIBLE = "visible" - - -class TextBaseline(Enum): - ALPHABETIC = "alphabetic" - IDEOGRAPHIC = "ideographic" - - -class TextThemeStyle(Enum): - DISPLAY_LARGE = "displayLarge" - DISPLAY_MEDIUM = "displayMedium" - DISPLAY_SMALL = "displaySmall" - HEADLINE_LARGE = "headlineLarge" - HEADLINE_MEDIUM = "headlineMedium" - HEADLINE_SMALL = "headlineSmall" - TITLE_LARGE = "titleLarge" - TITLE_MEDIUM = "titleMedium" - TITLE_SMALL = "titleSmall" - LABEL_LARGE = "labelLarge" - LABEL_MEDIUM = "labelMedium" - LABEL_SMALL = "labelSmall" - BODY_LARGE = "bodyLarge" - BODY_MEDIUM = "bodyMedium" - BODY_SMALL = "bodySmall" - - -class TextDecoration(IntFlag): - NONE = 0 - UNDERLINE = 1 - OVERLINE = 2 - LINE_THROUGH = 4 - - -class TextDecorationStyle(Enum): - SOLID = "solid" - DOUBLE = "double" - DOTTED = "dotted" - DASHED = "dashed" - WAVY = "wavy" - - -@dataclass -class TextStyle: - size: OptionalNumber = None - height: OptionalNumber = None - weight: Optional[FontWeight] = None - italic: Optional[bool] = None - decoration: Optional[TextDecoration] = None - decoration_color: Optional[ColorValue] = None - decoration_thickness: OptionalNumber = None - decoration_style: Optional[TextDecorationStyle] = None - font_family: Optional[str] = None - color: Optional[ColorValue] = None - bgcolor: Optional[ColorValue] = None - shadow: Union[None, BoxShadow, List[BoxShadow]] = None - foreground: Optional[Paint] = None - letter_spacing: OptionalNumber = None - word_spacing: OptionalNumber = None - overflow: Optional[TextOverflow] = None - baseline: Optional[TextBaseline] = None - - -@dataclass -class StrutStyle: - size: OptionalNumber = None - height: OptionalNumber = None - weight: Optional[FontWeight] = None - italic: Optional[bool] = None - font_family: Optional[str] = None - leading: OptionalNumber = None - force_strut_height: Optional[bool] = None diff --git a/sdk/python/packages/flet/src/flet/core/textfield.py b/sdk/python/packages/flet/src/flet/core/textfield.py deleted file mode 100644 index db24c94e5..000000000 --- a/sdk/python/packages/flet/src/flet/core/textfield.py +++ /dev/null @@ -1,839 +0,0 @@ -import dataclasses -import time -from enum import Enum -from typing import Any, List, Optional, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.animation import AnimationValue -from flet.core.autofill_group import AutofillHint -from flet.core.badge import BadgeValue -from flet.core.box import BoxConstraints -from flet.core.control import Control, OptionalNumber -from flet.core.form_field_control import FormFieldControl, InputBorder -from flet.core.ref import Ref -from flet.core.text_style import StrutStyle, TextStyle -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - BorderRadiusValue, - Brightness, - ClipBehavior, - ColorEnums, - ColorValue, - DurationValue, - IconValueOrControl, - MouseCursor, - OffsetValue, - OptionalControlEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, - TextAlign, - VerticalAlignment, -) - -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal - - -class KeyboardType(Enum): - NONE = "none" - TEXT = "text" - MULTILINE = "multiline" - NUMBER = "number" - PHONE = "phone" - DATETIME = "datetime" - EMAIL = "email" - URL = "url" - VISIBLE_PASSWORD = "visiblePassword" - NAME = "name" - STREET_ADDRESS = "streetAddress" - - -class TextCapitalization(Enum): - CHARACTERS = "characters" - WORDS = "words" - SENTENCES = "sentences" - - -@dataclasses.dataclass -class InputFilter: - regex_string: str - allow: bool = True - replacement_string: str = "" - multiline: bool = False - case_sensitive: bool = True - unicode: bool = False - dot_all: bool = False - - -class NumbersOnlyInputFilter(InputFilter): - def __init__(self): - super().__init__(regex_string=r"^[0-9]*$", allow=True, replacement_string="") - - -class TextOnlyInputFilter(InputFilter): - def __init__(self): - super().__init__(regex_string=r"^[a-zA-Z]*$", allow=True, replacement_string="") - - -class TextField(FormFieldControl, AdaptiveControl): - """ - A text field lets the user enter text, either with hardware keyboard or with an onscreen keyboard. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - def button_clicked(e): - t.value = f"Textboxes values are: '{tb1.value}', '{tb2.value}', '{tb3.value}', '{tb4.value}', '{tb5.value}'." - page.update() - - t = ft.Text() - tb1 = ft.TextField(label="Standard") - tb2 = ft.TextField(label="Disabled", disabled=True, value="First name") - tb3 = ft.TextField(label="Read-only", read_only=True, value="Last name") - tb4 = ft.TextField(label="With placeholder", hint_text="Please enter text here") - tb5 = ft.TextField(label="With an icon", icon=ft.icons.EMOJI_EMOTIONS) - b = ft.ElevatedButton(text="Submit", on_click=button_clicked) - page.add(tb1, tb2, tb3, tb4, tb5, b, t) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/textfield - """ - - def __init__( - self, - value: Optional[str] = None, - keyboard_type: Optional[KeyboardType] = None, - multiline: Optional[bool] = None, - min_lines: Optional[int] = None, - max_lines: Optional[int] = None, - max_length: Optional[int] = None, - password: Optional[bool] = None, - can_reveal_password: Optional[bool] = None, - read_only: Optional[bool] = None, - shift_enter: Optional[bool] = None, - text_align: Optional[TextAlign] = None, - autofocus: Optional[bool] = None, - capitalization: Optional[TextCapitalization] = None, - autocorrect: Optional[bool] = None, - enable_suggestions: Optional[bool] = None, - smart_dashes_type: Optional[bool] = None, - smart_quotes_type: Optional[bool] = None, - show_cursor: Optional[bool] = None, - cursor_color: Optional[ColorValue] = None, - cursor_error_color: Optional[ColorValue] = None, - cursor_width: OptionalNumber = None, - cursor_height: OptionalNumber = None, - cursor_radius: OptionalNumber = None, - selection_color: Optional[ColorValue] = None, - input_filter: Optional[InputFilter] = None, - obscuring_character: Optional[str] = None, - enable_interactive_selection: Optional[bool] = None, - enable_ime_personalized_learning: Optional[bool] = None, - can_request_focus: Optional[bool] = None, - ignore_pointers: Optional[bool] = None, - enable_scribble: Optional[bool] = None, - animate_cursor_opacity: Optional[bool] = None, - always_call_on_tap: Optional[bool] = None, - scroll_padding: Optional[PaddingValue] = None, - clip_behavior: Optional[ClipBehavior] = None, - keyboard_brightness: Optional[Brightness] = None, - mouse_cursor: Optional[MouseCursor] = None, - strut_style: Optional[StrutStyle] = None, - autofill_hints: Union[None, AutofillHint, List[AutofillHint]] = None, - on_change: OptionalControlEventCallable = None, - on_click: OptionalControlEventCallable = None, - on_submit: OptionalControlEventCallable = None, - on_focus: OptionalControlEventCallable = None, - on_blur: OptionalControlEventCallable = None, - on_tap_outside: OptionalControlEventCallable = None, - # - # FormField - # - text_size: OptionalNumber = None, - text_style: Optional[TextStyle] = None, - text_vertical_align: Union[VerticalAlignment, OptionalNumber] = None, - label: Optional[Union[str, Control]] = None, - label_style: Optional[TextStyle] = None, - icon: Optional[IconValueOrControl] = None, - border: Optional[InputBorder] = None, - color: Optional[ColorValue] = None, - bgcolor: Optional[ColorValue] = None, - border_radius: Optional[BorderRadiusValue] = None, - border_width: OptionalNumber = None, - border_color: Optional[ColorValue] = None, - focused_color: Optional[ColorValue] = None, - focused_bgcolor: Optional[ColorValue] = None, - focused_border_width: OptionalNumber = None, - focused_border_color: Optional[ColorValue] = None, - content_padding: Optional[PaddingValue] = None, - dense: Optional[bool] = None, - filled: Optional[bool] = None, - fill_color: Optional[ColorValue] = None, - hover_color: Optional[ColorValue] = None, - hint_text: Optional[str] = None, - hint_style: Optional[TextStyle] = None, - helper: Optional[Control] = None, - helper_text: Optional[str] = None, - helper_style: Optional[TextStyle] = None, - counter: Optional[Control] = None, - counter_text: Optional[str] = None, - counter_style: Optional[TextStyle] = None, - error: Optional[Control] = None, - error_text: Optional[str] = None, - error_style: Optional[TextStyle] = None, - prefix: Optional[Control] = None, - prefix_icon: Optional[IconValueOrControl] = None, - prefix_text: Optional[str] = None, - prefix_style: Optional[TextStyle] = None, - suffix: Optional[Control] = None, - suffix_icon: Optional[IconValueOrControl] = None, - suffix_text: Optional[str] = None, - suffix_style: Optional[TextStyle] = None, - focus_color: Optional[ColorValue] = None, - align_label_with_hint: Optional[bool] = None, - hint_fade_duration: Optional[DurationValue] = None, - hint_max_lines: Optional[int] = None, - helper_max_lines: Optional[int] = None, - error_max_lines: Optional[int] = None, - prefix_icon_size_constraints: Optional[BoxConstraints] = None, - suffix_icon_size_constraints: Optional[BoxConstraints] = None, - size_constraints: Optional[BoxConstraints] = None, - collapsed: Optional[bool] = None, - fit_parent_size: Optional[bool] = None, - # - # ConstrainedControl and AdaptiveControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - rtl: Optional[bool] = None, - adaptive: Optional[bool] = None, - ): - FormFieldControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - rtl=rtl, - # - # FormField - # - text_size=text_size, - text_style=text_style, - text_vertical_align=text_vertical_align, - label=label, - label_style=label_style, - icon=icon, - border=border, - color=color, - bgcolor=bgcolor, - border_radius=border_radius, - border_width=border_width, - border_color=border_color, - focused_color=focused_color, - focused_bgcolor=focused_bgcolor, - focused_border_width=focused_border_width, - focused_border_color=focused_border_color, - content_padding=content_padding, - dense=dense, - filled=filled, - fill_color=fill_color, - hover_color=hover_color, - hint_text=hint_text, - hint_style=hint_style, - helper=helper, - helper_text=helper_text, - helper_style=helper_style, - counter=counter, - counter_text=counter_text, - counter_style=counter_style, - error=error, - error_text=error_text, - error_style=error_style, - prefix=prefix, - prefix_icon=prefix_icon, - prefix_text=prefix_text, - prefix_style=prefix_style, - suffix=suffix, - suffix_icon=suffix_icon, - suffix_text=suffix_text, - suffix_style=suffix_style, - focus_color=focus_color, - align_label_with_hint=align_label_with_hint, - hint_fade_duration=hint_fade_duration, - hint_max_lines=hint_max_lines, - helper_max_lines=helper_max_lines, - error_max_lines=error_max_lines, - prefix_icon_size_constraints=prefix_icon_size_constraints, - suffix_icon_size_constraints=suffix_icon_size_constraints, - size_constraints=size_constraints, - collapsed=collapsed, - fit_parent_size=fit_parent_size, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.value = value - self.text_style = text_style - self.keyboard_type = keyboard_type - self.text_align = text_align - self.multiline = multiline - self.min_lines = min_lines - self.max_lines = max_lines - self.max_length = max_length - self.read_only = read_only - self.shift_enter = shift_enter - self.password = password - self.can_reveal_password = can_reveal_password - self.autofocus = autofocus - self.capitalization = capitalization - self.autocorrect = autocorrect - self.show_cursor = show_cursor - self.enable_suggestions = enable_suggestions - self.smart_dashes_type = smart_dashes_type - self.smart_quotes_type = smart_quotes_type - self.cursor_color = cursor_color - self.cursor_height = cursor_height - self.cursor_width = cursor_width - self.cursor_radius = cursor_radius - self.selection_color = selection_color - self.input_filter = input_filter - self.autofill_hints = autofill_hints - self.on_change = on_change - self.on_submit = on_submit - self.on_focus = on_focus - self.on_blur = on_blur - self.on_click = on_click - self.obscuring_character = obscuring_character - self.enable_scribble = enable_scribble - self.strut_style = strut_style - self.scroll_padding = scroll_padding - self.cursor_error_color = cursor_error_color - self.keyboard_brightness = keyboard_brightness - self.mouse_cursor = mouse_cursor - self.enable_interactive_selection = enable_interactive_selection - self.enable_ime_personalized_learning = enable_ime_personalized_learning - self.can_request_focus = can_request_focus - self.ignore_pointers = ignore_pointers - self.animate_cursor_opacity = animate_cursor_opacity - self.always_call_on_tap = always_call_on_tap - self.clip_behavior = clip_behavior - self.on_tap_outside = on_tap_outside - - def _get_control_name(self): - return "textfield" - - def before_update(self): - super().before_update() - assert ( - self.max_lines is None - or self.min_lines is None - or self.min_lines <= self.max_lines - ), "min_lines can't be greater than max_lines" - self._set_attr_json("inputFilter", self.__input_filter) - self._set_attr_json("autofillHints", self.__autofill_hints) - self._set_attr_json("scrollPadding", self.__scroll_padding) - self._set_attr_json("strutStyle", self.__strut_style) - if ( - ( - self.bgcolor is not None - or self.fill_color is not None - or self.hover_color is not None - or self.focused_color is not None - ) - ) and self.filled is None: - self.filled = True # required to display any of the above colors - - def focus(self): - self._set_attr_json("focus", str(time.time())) - self.update() - - def blur(self): - self._set_attr_json("blur", str(time.time())) - self.update() - - # value - @property - def value(self) -> Optional[str]: - return self._get_attr("value", def_value="") - - @value.setter - def value(self, value: Optional[str]): - self._set_attr("value", value) - - # strut_style - @property - def strut_style(self) -> Optional[TextStyle]: - return self.__strut_style - - @strut_style.setter - def strut_style(self, value: Optional[TextStyle]): - self.__strut_style = value - - # cursor_error_color - @property - def cursor_error_color(self) -> Optional[str]: - return self._get_attr("cursorErrorColor") - - @cursor_error_color.setter - def cursor_error_color(self, value: Optional[str]): - self._set_attr("cursorErrorColor", value) - - # enable_interactive_selection - @property - def enable_interactive_selection(self) -> bool: - return self._get_attr( - "enableInteractiveSelection", data_type="bool", def_value=True - ) - - @enable_interactive_selection.setter - def enable_interactive_selection(self, value: Optional[bool]): - self._set_attr("enableInteractiveSelection", value) - - # enable_ime_personalized_learning - @property - def enable_ime_personalized_learning(self) -> bool: - return self._get_attr( - "enableIMEPersonalizedLearning", data_type="bool", def_value=True - ) - - @enable_ime_personalized_learning.setter - def enable_ime_personalized_learning(self, value: Optional[bool]): - self._set_attr("enableIMEPersonalizedLearning", value) - - # animate_cursor_opacity - @property - def animate_cursor_opacity(self) -> Optional[bool]: - return self._get_attr("animateCursorOpacity", data_type="bool") - - @animate_cursor_opacity.setter - def animate_cursor_opacity(self, value: Optional[bool]): - self._set_attr("animateCursorOpacity", value) - - # keyboard_type - @property - def keyboard_type(self) -> Optional[KeyboardType]: - return self.__keyboard_type - - @keyboard_type.setter - def keyboard_type(self, value: Optional[KeyboardType]): - self.__keyboard_type = value - self._set_enum_attr("keyboardType", value, KeyboardType) - - # text_align - @property - def text_align(self) -> Optional[TextAlign]: - return self.__text_align - - @text_align.setter - def text_align(self, value: Optional[TextAlign]): - self.__text_align = value - self._set_enum_attr("textAlign", value, TextAlign) - - # multiline - @property - def multiline(self) -> bool: - return self._get_attr("multiline", data_type="bool", def_value=False) - - @multiline.setter - def multiline(self, value: Optional[bool]): - self._set_attr("multiline", value) - - # min_lines - @property - def min_lines(self) -> Optional[int]: - return self._get_attr("minLines") - - @min_lines.setter - def min_lines(self, value: Optional[int]): - assert value is None or value > 0, "min_lines must be greater than 0" - self._set_attr("minLines", value) - - # max_lines - @property - def max_lines(self) -> Optional[int]: - return self._get_attr("maxLines") - - @max_lines.setter - def max_lines(self, value: Optional[int]): - assert value is None or value > 0, "max_lines must be greater than 0" - self._set_attr("maxLines", value) - - # max_length - @property - def max_length(self) -> Optional[int]: - return self._get_attr("maxLength") - - @max_length.setter - def max_length(self, value: Optional[int]): - assert ( - value is None or value == -1 or value > 0 - ), "max_length must be either equal to -1 or greater than 0" - self._set_attr("maxLength", value) - - # obscuring_character - @property - def obscuring_character(self) -> Optional[str]: - return self._get_attr("obscuringCharacter", def_value="•") - - @obscuring_character.setter - def obscuring_character(self, value: Optional[str]): - self._set_attr("obscuringCharacter", value) - - # enable_scribble - @property - def enable_scribble(self) -> bool: - return self._get_attr("enableScribble", data_type="bool", def_value=True) - - @enable_scribble.setter - def enable_scribble(self, value: Optional[bool]): - self._set_attr("enableScribble", value) - - # scroll_padding - @property - def scroll_padding(self) -> Optional[PaddingValue]: - return self.__scroll_padding - - @scroll_padding.setter - def scroll_padding(self, value: Optional[PaddingValue]): - self.__scroll_padding = value - - # keyboard_brightness - @property - def keyboard_brightness(self) -> Optional[Brightness]: - return self.__keyboard_brightness - - @keyboard_brightness.setter - def keyboard_brightness(self, value: Optional[Brightness]): - self.__keyboard_brightness = value - self._set_enum_attr("keyboardBrightness", value, Brightness) - - # mouse_cursor - @property - def mouse_cursor(self) -> Optional[MouseCursor]: - return self.__mouse_cursor - - @mouse_cursor.setter - def mouse_cursor(self, value: Optional[MouseCursor]): - self.__mouse_cursor = value - self._set_enum_attr("mouseCursor", value, MouseCursor) - - # ignore_pointers - @property - def ignore_pointers(self) -> bool: - return self._get_attr("ignorePointers", data_type="bool", def_value=False) - - @ignore_pointers.setter - def ignore_pointers(self, value: Optional[bool]): - self._set_attr("ignorePointers", value) - - # clip_behavior - @property - def clip_behavior(self) -> Optional[ClipBehavior]: - return self.__clip_behavior - - @clip_behavior.setter - def clip_behavior(self, value: Optional[ClipBehavior]): - self.__clip_behavior = value - self._set_enum_attr("clipBehavior", value, ClipBehavior) - - # can_request_focus - @property - def can_request_focus(self) -> bool: - return self._get_attr("canRequestFocus", data_type="bool", def_value=True) - - @can_request_focus.setter - def can_request_focus(self, value: Optional[bool]): - self._set_attr("canRequestFocus", value) - - # always_call_on_tap - @property - def always_call_on_tap(self) -> bool: - return self._get_attr("alwaysCallOnTap", data_type="bool", def_value=False) - - @always_call_on_tap.setter - def always_call_on_tap(self, value: Optional[bool]): - self._set_attr("alwaysCallOnTap", value) - - # read_only - @property - def read_only(self) -> bool: - return self._get_attr("readOnly", data_type="bool", def_value=False) - - @read_only.setter - def read_only(self, value: Optional[bool]): - self._set_attr("readOnly", value) - - # shift_enter - @property - def shift_enter(self) -> bool: - return self._get_attr("shiftEnter", data_type="bool", def_value=False) - - @shift_enter.setter - def shift_enter(self, value: Optional[bool]): - self._set_attr("shiftEnter", value) - - # password - @property - def password(self) -> bool: - return self._get_attr("password", data_type="bool", def_value=False) - - @password.setter - def password(self, value: Optional[bool]): - self._set_attr("password", value) - - # can_reveal_password - @property - def can_reveal_password(self) -> bool: - return self._get_attr("canRevealPassword", data_type="bool", def_value=False) - - @can_reveal_password.setter - def can_reveal_password(self, value: Optional[bool]): - self._set_attr("canRevealPassword", value) - - # autofocus - @property - def autofocus(self) -> bool: - return self._get_attr("autofocus", data_type="bool", def_value=False) - - @autofocus.setter - def autofocus(self, value: Optional[bool]): - self._set_attr("autofocus", value) - - # capitalization - @property - def capitalization(self) -> Optional[TextCapitalization]: - return self.__capitalization - - @capitalization.setter - def capitalization(self, value: Optional[TextCapitalization]): - self.__capitalization = value - self._set_enum_attr("capitalization", value, TextCapitalization) - - # autocorrect - @property - def autocorrect(self) -> bool: - return self._get_attr("autocorrect", data_type="bool", def_value=True) - - @autocorrect.setter - def autocorrect(self, value: Optional[bool]): - self._set_attr("autocorrect", value) - - # show_cursor - @property - def show_cursor(self) -> bool: - return self._get_attr("showCursor", data_type="bool", def_value=True) - - @show_cursor.setter - def show_cursor(self, value: Optional[bool]): - self._set_attr("showCursor", value) - - # enable_suggestions - @property - def enable_suggestions(self) -> bool: - return self._get_attr("enableSuggestions", data_type="bool", def_value=True) - - @enable_suggestions.setter - def enable_suggestions(self, value: Optional[bool]): - self._set_attr("enableSuggestions", value) - - # smart_dashes_type - @property - def smart_dashes_type(self) -> bool: - return self._get_attr("smartDashesType", data_type="bool", def_value=True) - - @smart_dashes_type.setter - def smart_dashes_type(self, value: Optional[bool]): - self._set_attr("smartDashesType", value) - - # smart_quotes_type - @property - def smart_quotes_type(self) -> bool: - return self._get_attr("smartQuotesType", data_type="bool", def_value=True) - - @smart_quotes_type.setter - def smart_quotes_type(self, value: Optional[bool]): - self._set_attr("smartQuotesType", value) - - # cursor_color - @property - def cursor_color(self): - return self.__cursor_color - - @cursor_color.setter - def cursor_color(self, value): - self.__cursor_color = value - self._set_enum_attr("cursorColor", value, ColorEnums) - - # cursor_height - @property - def cursor_height(self) -> OptionalNumber: - return self._get_attr("cursorHeight") - - @cursor_height.setter - def cursor_height(self, value: OptionalNumber): - self._set_attr("cursorHeight", value) - - # cursor_width - @property - def cursor_width(self) -> OptionalNumber: - return self._get_attr("cursorWidth") - - @cursor_width.setter - def cursor_width(self, value: OptionalNumber): - self._set_attr("cursorWidth", value) - - # cursor_radius - @property - def cursor_radius(self) -> OptionalNumber: - return self._get_attr("cursorRadius") - - @cursor_radius.setter - def cursor_radius(self, value: OptionalNumber): - self._set_attr("cursorRadius", value) - - # selection_color - @property - def selection_color(self) -> Optional[ColorValue]: - return self.__selection_color - - @selection_color.setter - def selection_color(self, value: Optional[ColorValue]): - self.__selection_color = value - self._set_enum_attr("selectionColor", value, ColorEnums) - - # input_filter - @property - def input_filter(self) -> Optional[InputFilter]: - return self.__input_filter - - @input_filter.setter - def input_filter(self, value: Optional[InputFilter]): - self.__input_filter = value - - # autofill_hints - @property - def autofill_hints(self) -> Union[None, AutofillHint, List[AutofillHint]]: - return self.__autofill_hints - - @autofill_hints.setter - def autofill_hints(self, value: Union[None, AutofillHint, List[AutofillHint]]): - if value is not None: - if isinstance(value, List): - value = list( - map( - lambda x: x.value if isinstance(x, AutofillHint) else str(x), - value, - ) - ) - elif isinstance(value, AutofillHint): - value = value.value - self.__autofill_hints = value - - # on_change - @property - def on_change(self): - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - self._set_attr("onChange", True if handler is not None else None) - - # on_submit - @property - def on_submit(self) -> OptionalControlEventCallable: - return self._get_event_handler("submit") - - @on_submit.setter - def on_submit(self, handler: OptionalControlEventCallable): - self._add_event_handler("submit", handler) - - # on_focus - @property - def on_focus(self) -> OptionalControlEventCallable: - return self._get_event_handler("focus") - - @on_focus.setter - def on_focus(self, handler: OptionalControlEventCallable): - self._add_event_handler("focus", handler) - - # on_blur - @property - def on_blur(self) -> OptionalControlEventCallable: - return self._get_event_handler("blur") - - @on_blur.setter - def on_blur(self, handler: OptionalControlEventCallable): - self._add_event_handler("blur", handler) - - # on_click - @property - def on_click(self): - return self._get_event_handler("click") - - @on_click.setter - def on_click(self, handler: OptionalControlEventCallable): - self._add_event_handler("click", handler) - - # on_tap_outside - @property - def on_tap_outside(self) -> OptionalControlEventCallable: - return self._get_event_handler("tapOutside") - - @on_tap_outside.setter - def on_tap_outside(self, handler: OptionalControlEventCallable): - self._add_event_handler("tapOutside", handler) - self._set_attr("onTapOutside", True if handler is not None else None) diff --git a/sdk/python/packages/flet/src/flet/core/theme.py b/sdk/python/packages/flet/src/flet/core/theme.py deleted file mode 100644 index 804aae0c7..000000000 --- a/sdk/python/packages/flet/src/flet/core/theme.py +++ /dev/null @@ -1,1045 +0,0 @@ -from dataclasses import dataclass, field -from enum import Enum -from typing import List, Optional, Union - -from flet.core.alignment import Alignment -from flet.core.border import BorderSide -from flet.core.border_radius import BorderRadius -from flet.core.box import BoxConstraints, BoxDecoration, BoxShadow -from flet.core.buttons import ButtonStyle, OutlinedBorder -from flet.core.menu_bar import MenuStyle -from flet.core.navigation_bar import NavigationBarLabelBehavior -from flet.core.navigation_rail import NavigationRailLabelType -from flet.core.popup_menu_button import PopupMenuPosition -from flet.core.size import Size -from flet.core.slider import SliderInteraction -from flet.core.snack_bar import DismissDirection, SnackBarBehavior -from flet.core.text_style import TextStyle -from flet.core.textfield import TextCapitalization -from flet.core.tooltip import TooltipTriggerMode -from flet.core.types import ( - Brightness, - ClipBehavior, - ColorValue, - ControlState, - ControlStateValue, - DurationValue, - IconValue, - Locale, - MainAxisAlignment, - MarginValue, - MouseCursor, - NotchShape, - OffsetValue, - OptionalNumber, - PaddingValue, - StrokeCap, - TextAlign, - VisualDensity, -) -from flet.utils.deprecated import deprecated_class, deprecated_property - -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal - - -class PageTransitionTheme(Enum): - NONE = "none" - FADE_UPWARDS = "fadeUpwards" - OPEN_UPWARDS = "openUpwards" - ZOOM = "zoom" - CUPERTINO = "cupertino" - PREDICTIVE = "predictive" - FADE_FORWARDS = "fadeForwards" - - -@dataclass -class PageTransitionsTheme: - android: Optional[PageTransitionTheme] = None - ios: Optional[PageTransitionTheme] = None - linux: Optional[PageTransitionTheme] = None - macos: Optional[PageTransitionTheme] = None - windows: Optional[PageTransitionTheme] = None - - -@dataclass -class ColorScheme: - primary: Optional[ColorValue] = None - on_primary: Optional[ColorValue] = None - primary_container: Optional[ColorValue] = None - on_primary_container: Optional[ColorValue] = None - secondary: Optional[ColorValue] = None - on_secondary: Optional[ColorValue] = None - secondary_container: Optional[ColorValue] = None - on_secondary_container: Optional[ColorValue] = None - tertiary: Optional[ColorValue] = None - on_tertiary: Optional[ColorValue] = None - tertiary_container: Optional[ColorValue] = None - on_tertiary_container: Optional[ColorValue] = None - error: Optional[ColorValue] = None - on_error: Optional[ColorValue] = None - error_container: Optional[ColorValue] = None - on_error_container: Optional[ColorValue] = None - background: Optional[ColorValue] = None - on_background: Optional[ColorValue] = None - surface: Optional[ColorValue] = None - on_surface: Optional[ColorValue] = None - surface_variant: Optional[ColorValue] = None - on_surface_variant: Optional[ColorValue] = None - outline: Optional[ColorValue] = None - outline_variant: Optional[ColorValue] = None - shadow: Optional[ColorValue] = None - scrim: Optional[ColorValue] = None - inverse_surface: Optional[ColorValue] = None - on_inverse_surface: Optional[ColorValue] = None - inverse_primary: Optional[ColorValue] = None - surface_tint: Optional[ColorValue] = None - on_primary_fixed: Optional[ColorValue] = None - on_secondary_fixed: Optional[ColorValue] = None - on_tertiary_fixed: Optional[ColorValue] = None - on_primary_fixed_variant: Optional[ColorValue] = None - on_secondary_fixed_variant: Optional[ColorValue] = None - on_tertiary_fixed_variant: Optional[ColorValue] = None - primary_fixed: Optional[ColorValue] = None - secondary_fixed: Optional[ColorValue] = None - tertiary_fixed: Optional[ColorValue] = None - primary_fixed_dim: Optional[ColorValue] = None - secondary_fixed_dim: Optional[ColorValue] = None - surface_bright: Optional[ColorValue] = None - surface_container: Optional[ColorValue] = None - surface_container_high: Optional[ColorValue] = None - surface_container_low: Optional[ColorValue] = None - surface_container_lowest: Optional[ColorValue] = None - surface_dim: Optional[ColorValue] = None - tertiary_fixed_dim: Optional[ColorValue] = None - - -@dataclass -class TextTheme: - body_large: Optional[TextStyle] = None - body_medium: Optional[TextStyle] = None - body_small: Optional[TextStyle] = None - display_large: Optional[TextStyle] = None - display_medium: Optional[TextStyle] = None - display_small: Optional[TextStyle] = None - headline_large: Optional[TextStyle] = None - headline_medium: Optional[TextStyle] = None - headline_small: Optional[TextStyle] = None - label_large: Optional[TextStyle] = None - label_medium: Optional[TextStyle] = None - label_small: Optional[TextStyle] = None - title_large: Optional[TextStyle] = None - title_medium: Optional[TextStyle] = None - title_small: Optional[TextStyle] = None - - -@dataclass -class ScrollbarTheme: - thumb_visibility: ControlStateValue[bool] = None - thickness: ControlStateValue[OptionalNumber] = None - track_visibility: ControlStateValue[bool] = None - radius: Optional[float] = None - thumb_color: ControlStateValue[ColorValue] = None - track_color: ControlStateValue[ColorValue] = None - track_border_color: ControlStateValue[ColorValue] = None - cross_axis_margin: Optional[float] = None - main_axis_margin: Optional[float] = None - min_thumb_length: Optional[float] = None - interactive: Optional[bool] = None - - def __post_init__(self): - if not isinstance(self.thumb_visibility, dict): - self.thumb_visibility = {ControlState.DEFAULT: self.thumb_visibility} - if not isinstance(self.thickness, dict): - self.thickness = {ControlState.DEFAULT: self.thickness} - if not isinstance(self.track_visibility, dict): - self.track_visibility = {ControlState.DEFAULT: self.track_visibility} - if not isinstance(self.thumb_color, dict): - self.thumb_color = {ControlState.DEFAULT: self.thumb_color} - if not isinstance(self.track_color, dict): - self.track_color = {ControlState.DEFAULT: self.track_color} - if not isinstance(self.track_border_color, dict): - self.track_border_color = {ControlState.DEFAULT: self.track_border_color} - - -@dataclass -class TabsTheme: - divider_color: Optional[ColorValue] = None - indicator_border_radius: Optional[BorderRadius] = None - indicator_border_side: Optional[BorderSide] = None - indicator_padding: Optional[PaddingValue] = None - indicator_color: Optional[ColorValue] = None - indicator_tab_size: Optional[bool] = None - label_color: Optional[ColorValue] = None - unselected_label_color: Optional[ColorValue] = None - overlay_color: ControlStateValue[ColorValue] = None - mouse_cursor: ControlStateValue[MouseCursor] = None - label_padding: Optional[PaddingValue] = None - label_text_style: Optional[TextStyle] = None - unselected_label_text_style: Optional[TextStyle] = None - - def __post_init__(self): - if not isinstance(self.overlay_color, dict): - self.overlay_color = {ControlState.DEFAULT: self.overlay_color} - if not isinstance(self.mouse_cursor, dict): - self.mouse_cursor = {ControlState.DEFAULT: self.mouse_cursor} - - -@dataclass -class SystemOverlayStyle: - status_bar_color: Optional[ColorValue] = None - system_navigation_bar_color: Optional[ColorValue] = None - system_navigation_bar_divider_color: Optional[ColorValue] = None - enforce_system_navigation_bar_contrast: Optional[bool] = None - enforce_system_status_bar_contrast: Optional[bool] = None - system_navigation_bar_icon_brightness: Optional[Brightness] = None - status_bar_brightness: Optional[Brightness] = None - status_bar_icon_brightness: Optional[Brightness] = None - - -@dataclass -class DialogTheme: - bgcolor: Optional[ColorValue] = None - shadow_color: Optional[ColorValue] = None - surface_tint_color: Optional[ColorValue] = None - icon_color: Optional[ColorValue] = None - elevation: OptionalNumber = None - shape: Optional[OutlinedBorder] = None - title_text_style: Optional[TextStyle] = None - content_text_style: Optional[TextStyle] = None - alignment: Optional[Alignment] = None - actions_padding: Optional[PaddingValue] = None - clip_behavior: Optional[ClipBehavior] = None - barrier_color: Optional[ColorValue] = None - inset_padding: Optional[PaddingValue] = None - - -@dataclass -class ElevatedButtonTheme: - bgcolor: Optional[ColorValue] = None - foreground_color: Optional[ColorValue] = None - icon_color: Optional[ColorValue] = None - shadow_color: Optional[ColorValue] = None - disabled_bgcolor: Optional[ColorValue] = None - disabled_foreground_color: Optional[ColorValue] = None - disabled_icon_color: Optional[ColorValue] = None - overlay_color: Optional[ColorValue] = None - surface_tint_color: Optional[ColorValue] = None - elevation: OptionalNumber = None - padding: Optional[PaddingValue] = None - enable_feedback: Optional[bool] = None - disabled_mouse_cursor: Optional[MouseCursor] = None - enabled_mouse_cursor: Optional[MouseCursor] = None - shape: Optional[OutlinedBorder] = None - text_style: Optional[TextStyle] = None - visual_density: Optional[VisualDensity] = None - border_side: Optional[BorderSide] = None - animation_duration: Optional[DurationValue] = None - alignment: Optional[Alignment] = None - icon_size: OptionalNumber = None - fixed_size: Optional[Size] = None - maximum_size: Optional[Size] = None - minimum_size: Optional[Size] = None - - -@dataclass -class OutlinedButtonTheme(ElevatedButtonTheme): - pass - - -@dataclass -class TextButtonTheme(ElevatedButtonTheme): - pass - - -@dataclass -class FilledButtonTheme(ElevatedButtonTheme): - pass - - -@dataclass -class IconButtonTheme: - # from ElevatedButtonTheme (excluding icon_color, disabled_icon_color, text_style) - bgcolor: Optional[ColorValue] = None - foreground_color: Optional[ColorValue] = None - shadow_color: Optional[ColorValue] = None - disabled_bgcolor: Optional[ColorValue] = None - disabled_foreground_color: Optional[ColorValue] = None - overlay_color: Optional[ColorValue] = None - surface_tint_color: Optional[ColorValue] = None - elevation: OptionalNumber = None - padding: Optional[PaddingValue] = None - enable_feedback: Optional[bool] = None - disabled_mouse_cursor: Optional[MouseCursor] = None - enabled_mouse_cursor: Optional[MouseCursor] = None - shape: Optional[OutlinedBorder] = None - visual_density: Optional[VisualDensity] = None - border_side: Optional[BorderSide] = None - animation_duration: Optional[DurationValue] = None - alignment: Optional[Alignment] = None - icon_size: OptionalNumber = None - fixed_size: Optional[Size] = None - maximum_size: Optional[Size] = None - minimum_size: Optional[Size] = None - # Icon Button Theme - focus_color: Optional[ColorValue] = None - highlight_color: Optional[ColorValue] = None - hover_color: Optional[ColorValue] = None - - -@dataclass -class BottomSheetTheme: - bgcolor: Optional[ColorValue] = None - shadow_color: Optional[ColorValue] = None - surface_tint_color: Optional[ColorValue] = None - drag_handle_color: Optional[ColorValue] = None - elevation: OptionalNumber = None - shape: Optional[OutlinedBorder] = None - show_drag_handle: Optional[bool] = None - modal_bgcolor: Optional[ColorValue] = None - modal_elevation: OptionalNumber = None - clip_behavior: Optional[ClipBehavior] = None - size_constraints: Optional[BoxConstraints] = None - modal_barrier_color: Optional[ColorValue] = None - - -@dataclass -class CardTheme: - color: Optional[ColorValue] = None - shadow_color: Optional[ColorValue] = None - surface_tint_color: Optional[ColorValue] = None - elevation: OptionalNumber = None - shape: Optional[OutlinedBorder] = None - clip_behavior: Optional[ClipBehavior] = None - margin: Optional[MarginValue] = None - - -@dataclass -class ChipTheme: - color: ControlStateValue[ColorValue] = None - bgcolor: Optional[ColorValue] = None - shadow_color: Optional[ColorValue] = None - surface_tint_color: Optional[ColorValue] = None - disabled_color: Optional[ColorValue] = None - selected_color: Optional[ColorValue] = None - checkmark_color: Optional[ColorValue] = None - delete_icon_color: Optional[ColorValue] = None - secondary_selected_color: Optional[ColorValue] = None - selected_shadow_color: Optional[ColorValue] = None - elevation: OptionalNumber = None - click_elevation: OptionalNumber = None - shape: Optional[OutlinedBorder] = None - padding: Optional[PaddingValue] = None - label_padding: Optional[PaddingValue] = None - label_text_style: Optional[TextStyle] = None - secondary_label_text_style: Optional[TextStyle] = None - border_side: Optional[BorderSide] = None - brightness: Optional[Brightness] = None - show_checkmark: Optional[bool] = None - avatar_constraints: Optional[BoxConstraints] = None - delete_icon_size_constraints: Optional[BoxConstraints] = None - - def __post_init__(self): - if not isinstance(self.color, dict): - self.color = {ControlState.DEFAULT: self.color} - - -@dataclass -class FloatingActionButtonTheme: - bgcolor: Optional[ColorValue] = None - hover_color: Optional[ColorValue] = None - focus_color: Optional[ColorValue] = None - foreground_color: Optional[ColorValue] = None - splash_color: Optional[ColorValue] = None - elevation: OptionalNumber = None - focus_elevation: OptionalNumber = None - hover_elevation: OptionalNumber = None - highlight_elevation: OptionalNumber = None - disabled_elevation: OptionalNumber = None - shape: Optional[OutlinedBorder] = None - enable_feedback: Optional[bool] = None - extended_padding: Optional[PaddingValue] = None - extended_text_style: Optional[TextStyle] = None - extended_icon_label_spacing: OptionalNumber = None - extended_size_constraints: Optional[BoxConstraints] = None - size_constraints: Optional[BoxConstraints] = None - large_size_constraints: Optional[BoxConstraints] = None - small_size_constraints: Optional[BoxConstraints] = None - - -@dataclass -class NavigationRailTheme: - bgcolor: Optional[ColorValue] = None - indicator_color: Optional[ColorValue] = None - elevation: OptionalNumber = None - indicator_shape: Optional[OutlinedBorder] = None - unselected_label_text_style: Optional[TextStyle] = None - selected_label_text_style: Optional[TextStyle] = None - label_type: Optional[NavigationRailLabelType] = None - min_width: OptionalNumber = None - min_extended_width: OptionalNumber = None - group_alignment: OptionalNumber = None - use_indicator: Optional[bool] = None - - -@dataclass -class AppBarTheme: - color: Optional[ColorValue] = None - bgcolor: Optional[ColorValue] = None - shadow_color: Optional[ColorValue] = None - surface_tint_color: Optional[ColorValue] = None - foreground_color: Optional[ColorValue] = None - elevation: OptionalNumber = None - shape: Optional[OutlinedBorder] = None - title_text_style: Optional[TextStyle] = None - toolbar_text_style: Optional[TextStyle] = None - center_title: Optional[bool] = None - title_spacing: OptionalNumber = None - scroll_elevation: OptionalNumber = None - toolbar_height: OptionalNumber = None - actions_padding: Optional[PaddingValue] = None - - -@dataclass -class BottomAppBarTheme: - color: Optional[ColorValue] = None - shadow_color: Optional[ColorValue] = None - surface_tint_color: Optional[ColorValue] = None - elevation: OptionalNumber = None - height: OptionalNumber = None - padding: Optional[PaddingValue] = None - shape: Optional[NotchShape] = None - - -@dataclass -class RadioTheme: - fill_color: ControlStateValue[ColorValue] = None - overlay_color: ControlStateValue[ColorValue] = None - splash_radius: OptionalNumber = None - height: OptionalNumber = None - visual_density: Optional[VisualDensity] = None - mouse_cursor: ControlStateValue[MouseCursor] = None - - def __post_init__(self): - if not isinstance(self.fill_color, dict): - self.fill_color = {ControlState.DEFAULT: self.fill_color} - if not isinstance(self.overlay_color, dict): - self.overlay_color = {ControlState.DEFAULT: self.overlay_color} - if not isinstance(self.mouse_cursor, dict): - self.mouse_cursor = {ControlState.DEFAULT: self.mouse_cursor} - - -@dataclass -class CheckboxTheme: - overlay_color: ControlStateValue[ColorValue] = None - check_color: ControlStateValue[ColorValue] = None - fill_color: ControlStateValue[ColorValue] = None - splash_radius: OptionalNumber = None - border_side: Optional[BorderSide] = None - visual_density: Optional[VisualDensity] = None - shape: Optional[OutlinedBorder] = None - mouse_cursor: ControlStateValue[MouseCursor] = None - - def __post_init__(self): - if not isinstance(self.overlay_color, dict): - self.overlay_color = {ControlState.DEFAULT: self.overlay_color} - if not isinstance(self.check_color, dict): - self.check_color = {ControlState.DEFAULT: self.check_color} - if not isinstance(self.fill_color, dict): - self.fill_color = {ControlState.DEFAULT: self.fill_color} - if not isinstance(self.mouse_cursor, dict): - self.mouse_cursor = {ControlState.DEFAULT: self.mouse_cursor} - - -@dataclass -class BadgeTheme: - bgcolor: Optional[ColorValue] = None - text_color: Optional[ColorValue] = None - small_size: OptionalNumber = None - large_size: OptionalNumber = None - alignment: Optional[Alignment] = None - padding: Optional[PaddingValue] = None - offset: Optional[OffsetValue] = None - text_style: Optional[TextStyle] = None - - -@dataclass -class SwitchTheme: - thumb_color: ControlStateValue[ColorValue] = None - track_color: ControlStateValue[ColorValue] = None - overlay_color: ControlStateValue[ColorValue] = None - track_outline_color: ControlStateValue[ColorValue] = None - thumb_icon: ControlStateValue[str] = None - track_outline_width: ControlStateValue[OptionalNumber] = None - splash_radius: OptionalNumber = None - mouse_cursor: ControlStateValue[MouseCursor] = None - padding: Optional[PaddingValue] = None - - def __post_init__(self): - if not isinstance(self.thumb_color, dict): - self.thumb_color = {ControlState.DEFAULT: self.thumb_color} - if not isinstance(self.track_color, dict): - self.track_color = {ControlState.DEFAULT: self.track_color} - if not isinstance(self.overlay_color, dict): - self.overlay_color = {ControlState.DEFAULT: self.overlay_color} - if not isinstance(self.track_outline_color, dict): - self.track_outline_color = {ControlState.DEFAULT: self.track_outline_color} - if not isinstance(self.thumb_icon, dict): - self.thumb_icon = {ControlState.DEFAULT: self.thumb_icon} - if not isinstance(self.track_outline_width, dict): - self.track_outline_width = {ControlState.DEFAULT: self.track_outline_width} - if not isinstance(self.mouse_cursor, dict): - self.mouse_cursor = {ControlState.DEFAULT: self.mouse_cursor} - - -@dataclass -class DividerTheme: - color: Optional[ColorValue] = None - thickness: OptionalNumber = None - space: OptionalNumber = None - leading_indent: OptionalNumber = None - trailing_indent: OptionalNumber = None - - -@dataclass -class SnackBarTheme: - bgcolor: Optional[ColorValue] = None - action_text_color: Optional[ColorValue] = None - action_bgcolor: Optional[ColorValue] = None - close_icon_color: Optional[ColorValue] = None - disabled_action_text_color: Optional[ColorValue] = None - disabled_action_bgcolor: Optional[ColorValue] = None - elevation: OptionalNumber = None - content_text_style: Optional[TextStyle] = None - width: OptionalNumber = None - alignment: Optional[Alignment] = None - show_close_icon: Optional[bool] = None - dismiss_direction: Optional[DismissDirection] = None - behavior: Optional[SnackBarBehavior] = None - shape: Optional[OutlinedBorder] = None - inset_padding: Optional[PaddingValue] = None - action_overflow_threshold: OptionalNumber = None - - -@dataclass -class BannerTheme: - bgcolor: Optional[ColorValue] = None - surface_tint_color: Optional[ColorValue] = None - shadow_color: Optional[ColorValue] = None - divider_color: Optional[ColorValue] = None - padding: Optional[PaddingValue] = None - leading_padding: Optional[PaddingValue] = None - elevation: OptionalNumber = None - content_text_style: Optional[TextStyle] = None - - -@dataclass -class DatePickerTheme: - bgcolor: Optional[ColorValue] = None - surface_tint_color: Optional[ColorValue] = None - shadow_color: Optional[ColorValue] = None - divider_color: Optional[ColorValue] = None - header_bgcolor: Optional[ColorValue] = None - today_bgcolor: ControlStateValue[ColorValue] = None - day_bgcolor: ControlStateValue[ColorValue] = None - day_overlay_color: ControlStateValue[ColorValue] = None - day_foreground_color: ControlStateValue[ColorValue] = None - elevation: OptionalNumber = None - range_picker_elevation: OptionalNumber = None - day_text_style: Optional[TextStyle] = None - weekday_text_style: Optional[TextStyle] = None - year_text_style: Optional[TextStyle] = None - shape: Optional[OutlinedBorder] = None - cancel_button_style: Optional[ButtonStyle] = None - confirm_button_style: Optional[ButtonStyle] = None - header_foreground_color: Optional[ColorValue] = None - header_headline_text_style: Optional[TextStyle] = None - header_help_text_style: Optional[TextStyle] = None - range_picker_bgcolor: Optional[ColorValue] = None - range_picker_header_bgcolor: Optional[ColorValue] = None - range_picker_header_foreground_color: Optional[ColorValue] = None - today_foreground_color: ControlStateValue[ColorValue] = None - range_picker_shape: Optional[OutlinedBorder] = None - range_picker_header_help_text_style: Optional[TextStyle] = None - range_picker_header_headline_text_style: Optional[TextStyle] = None - range_picker_surface_tint_color: Optional[ColorValue] = None - range_selection_bgcolor: Optional[ColorValue] = None - range_selection_overlay_color: ControlStateValue[ColorValue] = None - today_border_side: Optional[BorderSide] = None - year_bgcolor: ControlStateValue[ColorValue] = None - year_foreground_color: ControlStateValue[ColorValue] = None - year_overlay_color: ControlStateValue[ColorValue] = None - day_shape: ControlStateValue[OutlinedBorder] = None - locale: Optional[Locale] = None - - def __post_init__(self): - if not isinstance(self.today_bgcolor, dict): - self.today_bgcolor = {ControlState.DEFAULT: self.today_bgcolor} - if not isinstance(self.day_bgcolor, dict): - self.day_bgcolor = {ControlState.DEFAULT: self.day_bgcolor} - if not isinstance(self.day_overlay_color, dict): - self.day_overlay_color = {ControlState.DEFAULT: self.day_overlay_color} - if not isinstance(self.day_foreground_color, dict): - self.day_foreground_color = { - ControlState.DEFAULT: self.day_foreground_color - } - if not isinstance(self.today_foreground_color, dict): - self.today_foreground_color = { - ControlState.DEFAULT: self.today_foreground_color - } - if not isinstance(self.range_selection_overlay_color, dict): - self.range_selection_overlay_color = { - ControlState.DEFAULT: self.range_selection_overlay_color - } - if not isinstance(self.year_bgcolor, dict): - self.year_bgcolor = {ControlState.DEFAULT: self.year_bgcolor} - if not isinstance(self.year_foreground_color, dict): - self.year_foreground_color = { - ControlState.DEFAULT: self.year_foreground_color - } - if not isinstance(self.year_overlay_color, dict): - self.year_overlay_color = {ControlState.DEFAULT: self.year_overlay_color} - if not isinstance(self.day_shape, dict): - self.day_shape = {ControlState.DEFAULT: self.day_shape} - - -@dataclass -class TimePickerTheme: - bgcolor: Optional[ColorValue] = None - day_period_color: Optional[ColorValue] = None - day_period_text_color: Optional[ColorValue] = None - dial_bgcolor: Optional[ColorValue] = None - dial_hand_color: Optional[ColorValue] = None - dial_text_color: Optional[ColorValue] = None - entry_mode_icon_color: Optional[ColorValue] = None - hour_minute_color: Optional[ColorValue] = None - hour_minute_text_color: Optional[ColorValue] = None - day_period_button_style: Optional[ButtonStyle] = None - cancel_button_style: Optional[ButtonStyle] = None - confirm_button_style: Optional[ButtonStyle] = None - day_period_text_style: Optional[TextStyle] = None - dial_text_style: Optional[TextStyle] = None - help_text_style: Optional[TextStyle] = None - hour_minute_text_style: Optional[TextStyle] = None - elevation: OptionalNumber = None - shape: Optional[OutlinedBorder] = None - day_period_shape: Optional[OutlinedBorder] = None - hour_minute_shape: Optional[OutlinedBorder] = None - day_period_border_side: Optional[BorderSide] = None - padding: Optional[PaddingValue] = None - time_selector_separator_color: ControlStateValue[ColorValue] = None - time_selector_separator_text_style: ControlStateValue[TextStyle] = None - - def __post_init__(self): - if not isinstance(self.time_selector_separator_color, dict): - self.time_selector_separator_color = { - ControlState.DEFAULT: self.time_selector_separator_color - } - if not isinstance(self.time_selector_separator_text_style, dict): - self.time_selector_separator_text_style = { - ControlState.DEFAULT: self.time_selector_separator_text_style - } - - -@dataclass -class DropdownMenuTheme: - menu_style: Optional[MenuStyle] = None - text_style: Optional[TextStyle] = None - - -@dataclass -class ListTileTheme: - icon_color: Optional[ColorValue] = None - text_color: Optional[ColorValue] = None - bgcolor: Optional[ColorValue] = None - selected_tile_color: Optional[ColorValue] = None - selected_color: Optional[ColorValue] = None - is_three_line: Optional[bool] = None - enable_feedback: Optional[bool] = None - dense: Optional[bool] = None - shape: Optional[OutlinedBorder] = None - visual_density: Optional[VisualDensity] = None - content_padding: Optional[PaddingValue] = None - min_vertical_padding: Optional[PaddingValue] = None - horizontal_spacing: OptionalNumber = None - min_leading_width: OptionalNumber = None - title_text_style: Optional[TextStyle] = None - subtitle_text_style: Optional[TextStyle] = None - leading_and_trailing_text_style: Optional[TextStyle] = None - mouse_cursor: ControlStateValue[MouseCursor] = None - min_tile_height: OptionalNumber = None - - def __post_init__(self): - if not isinstance(self.mouse_cursor, dict): - self.mouse_cursor = {ControlState.DEFAULT: self.mouse_cursor} - - -@dataclass -class TooltipTheme: - height: OptionalNumber = None - text_style: Optional[TextStyle] = None - enable_feedback: Optional[bool] = None - exclude_from_semantics: Optional[bool] = None - prefer_below: Optional[bool] = None - vertical_offset: OptionalNumber = None - padding: Optional[PaddingValue] = None - wait_duration: Optional[DurationValue] = None - exit_duration: Optional[DurationValue] = None - show_duration: Optional[DurationValue] = None - margin: Optional[MarginValue] = None - trigger_mode: Optional[TooltipTriggerMode] = None - decoration: Optional[BoxDecoration] = None - text_align: Optional[TextAlign] = None - - -@dataclass -class ExpansionTileTheme: - bgcolor: Optional[ColorValue] = None - icon_color: Optional[ColorValue] = None - text_color: Optional[ColorValue] = None - collapsed_bgcolor: Optional[ColorValue] = None - collapsed_icon_color: Optional[ColorValue] = None - clip_behavior: Optional[ClipBehavior] = None - collapsed_text_color: Optional[ColorValue] = None - tile_padding: Optional[PaddingValue] = None - expanded_alignment: Optional[Alignment] = None - controls_padding: Optional[PaddingValue] = None - - -@dataclass -class SliderTheme: - active_track_color: Optional[ColorValue] = None - inactive_track_color: Optional[ColorValue] = None - thumb_color: Optional[ColorValue] = None - overlay_color: Optional[ColorValue] = None - value_indicator_color: Optional[ColorValue] = None - disabled_thumb_color: Optional[ColorValue] = None - value_indicator_text_style: Optional[TextStyle] = None - mouse_cursor: ControlStateValue[MouseCursor] = None - active_tick_mark_color: Optional[ColorValue] = None - disabled_active_tick_mark_color: Optional[ColorValue] = None - disabled_active_track_color: Optional[ColorValue] = None - disabled_inactive_tick_mark_color: Optional[ColorValue] = None - disabled_inactive_track_color: Optional[ColorValue] = None - disabled_secondary_active_track_color: Optional[ColorValue] = None - inactive_tick_mark_color: Optional[ColorValue] = None - overlapping_shape_stroke_color: Optional[ColorValue] = None - min_thumb_separation: OptionalNumber = None - secondary_active_track_color: Optional[ColorValue] = None - track_height: OptionalNumber = None - value_indicator_stroke_color: Optional[ColorValue] = None - interaction: Optional[SliderInteraction] = None - padding: Optional[PaddingValue] = None - track_gap: OptionalNumber = None - thumb_size: ControlStateValue[Size] = None - year_2023: Optional[bool] = None - - def __post_init__(self): - if not isinstance(self.mouse_cursor, dict): - self.mouse_cursor = {ControlState.DEFAULT: self.mouse_cursor} - if not isinstance(self.thumb_size, dict): - self.thumb_size = {ControlState.DEFAULT: self.thumb_size} - if self.year_2023 is not None: - deprecated_property( - name="year_2023", - version="0.27.0", - delete_version=None, # not known for now - reason="Set this flag to False to opt into the 2024 Slider appearance. In the future, this flag will default to False.", - ) - - -@dataclass -class ProgressIndicatorTheme: - color: Optional[ColorValue] = None - circular_track_color: Optional[ColorValue] = None - linear_track_color: Optional[ColorValue] = None - refresh_bgcolor: Optional[ColorValue] = None - linear_min_height: OptionalNumber = None - border_radius: Optional[BorderRadius] = None - track_gap: OptionalNumber = None - circular_track_padding: Optional[PaddingValue] = None - size_constraints: Optional[BoxConstraints] = None - stop_indicator_color: Optional[ColorValue] = None - stop_indicator_radius: OptionalNumber = None - stroke_align: OptionalNumber = None - stroke_cap: Optional[StrokeCap] = None - stroke_width: OptionalNumber = None - year_2023: Optional[bool] = None - - def __post_init__(self): - if self.year_2023 is not None: - deprecated_property( - name="year_2023", - version="0.27.0", - delete_version=None, # not known for now - reason="Set this flag to False to opt into the 2024 ProgressIndicator appearance. In the future, this flag will default to False.", - ) - - -@dataclass -class PopupMenuTheme: - color: Optional[ColorValue] = None - surface_tint_color: Optional[ColorValue] = None - shadow_color: Optional[ColorValue] = None - icon_color: Optional[ColorValue] = None - text_style: Optional[TextStyle] = None - label_text_style: Optional[TextStyle] = None - enable_feedback: Optional[bool] = None - elevation: OptionalNumber = None - icon_size: OptionalNumber = None - shape: Optional[OutlinedBorder] = None - menu_position: Optional[PopupMenuPosition] = None - mouse_cursor: ControlStateValue[MouseCursor] = None - menu_padding: Optional[PaddingValue] = None - - def __post_init__(self): - if not isinstance(self.mouse_cursor, dict): - self.mouse_cursor = {ControlState.DEFAULT: self.mouse_cursor} - if not isinstance(self.thumb_size, dict): - self.thumb_size = {ControlState.DEFAULT: self.thumb_size} - if self.year_2023 is not None: - deprecated_property( - name="year_2023", - version="0.27.0", - delete_version=None, # not known for now - reason="Set this flag to False to opt into the 2024 Slider appearance. In the future, this flag will default to False.", - ) - - -@dataclass -class SearchBarTheme: - bgcolor: Optional[ColorValue] = None - text_capitalization: Optional[TextCapitalization] = None - shadow_color: ControlStateValue[ColorValue] = None - surface_tint_color: ControlStateValue[ColorValue] = None - overlay_color: ControlStateValue[ColorValue] = None - elevation: ControlStateValue[OptionalNumber] = None - text_style: ControlStateValue[TextStyle] = None - hint_style: ControlStateValue[TextStyle] = None - shape: ControlStateValue[OutlinedBorder] = None - padding: ControlStateValue[PaddingValue] = None - size_constraints: Optional[BoxConstraints] = None - border_side: ControlStateValue[BorderSide] = None - - def __post_init__(self): - if not isinstance(self.shadow_color, dict): - self.shadow_color = {ControlState.DEFAULT: self.shadow_color} - if not isinstance(self.surface_tint_color, dict): - self.surface_tint_color = {ControlState.DEFAULT: self.surface_tint_color} - if not isinstance(self.overlay_color, dict): - self.overlay_color = {ControlState.DEFAULT: self.overlay_color} - if not isinstance(self.elevation, dict): - self.elevation = {ControlState.DEFAULT: self.elevation} - if not isinstance(self.text_style, dict): - self.text_style = {ControlState.DEFAULT: self.text_style} - if not isinstance(self.hint_style, dict): - self.hint_style = {ControlState.DEFAULT: self.hint_style} - if not isinstance(self.shape, dict): - self.shape = {ControlState.DEFAULT: self.shape} - if not isinstance(self.padding, dict): - self.padding = {ControlState.DEFAULT: self.padding} - if not isinstance(self.border_side, dict): - self.border_side = {ControlState.DEFAULT: self.border_side} - - -@dataclass -class SearchViewTheme: - bgcolor: Optional[ColorValue] = None - surface_tint_color: Optional[ColorValue] = None - divider_color: Optional[ColorValue] = None - elevation: OptionalNumber = None - header_hint_text_style: Optional[TextStyle] = None - header_text_style: Optional[TextStyle] = None - shape: Optional[OutlinedBorder] = None - border_side: Optional[BorderSide] = None - size_constraints: Optional[BoxConstraints] = None - header_height: OptionalNumber = None - padding: Optional[PaddingValue] = None - bar_padding: Optional[PaddingValue] = None - shrink_wrap: Optional[bool] = None - - -@dataclass -class NavigationDrawerTheme: - bgcolor: Optional[ColorValue] = None - shadow_color: Optional[ColorValue] = None - surface_tint_color: Optional[ColorValue] = None - indicator_color: Optional[ColorValue] = None - elevation: OptionalNumber = None - tile_height: OptionalNumber = None - label_text_style: ControlStateValue[TextStyle] = None - indicator_shape: Optional[OutlinedBorder] = None - indicator_size: Optional[Size] = None - - def __post_init__(self): - if not isinstance(self.label_text_style, dict): - self.label_text_style = {ControlState.DEFAULT: self.label_text_style} - - -@dataclass -class NavigationBarTheme: - bgcolor: Optional[ColorValue] = None - shadow_color: Optional[ColorValue] = None - surface_tint_color: Optional[ColorValue] = None - indicator_color: Optional[ColorValue] = None - overlay_color: ControlStateValue[ColorValue] = None - elevation: OptionalNumber = None - height: OptionalNumber = None - label_text_style: ControlStateValue[TextStyle] = None - indicator_shape: Optional[OutlinedBorder] = None - label_behavior: Optional[NavigationBarLabelBehavior] = None - label_padding: Optional[PaddingValue] = None - - def __post_init__(self): - if not isinstance(self.label_text_style, dict): - self.label_text_style = {ControlState.DEFAULT: self.label_text_style} - if not isinstance(self.overlay_color, dict): - self.overlay_color = {ControlState.DEFAULT: self.overlay_color} - - -@dataclass -class SegmentedButtonTheme: - selected_icon: Optional[IconValue] = None - style: Optional[ButtonStyle] = None - - -@dataclass -class IconTheme: - color: Optional[ColorValue] = None - apply_text_scaling: Optional[bool] = None - fill: OptionalNumber = None - opacity: OptionalNumber = None - size: OptionalNumber = None - optical_size: OptionalNumber = None - grade: OptionalNumber = None - weight: OptionalNumber = None - shadows: Optional[List[BoxShadow]] = None - - -@dataclass -class DataTableTheme: - checkbox_horizontal_margin: OptionalNumber = None - column_spacing: OptionalNumber = None - data_row_max_height: OptionalNumber = None - data_row_min_height: OptionalNumber = None - data_row_color: ControlStateValue[ColorValue] = None - data_text_style: Optional[TextStyle] = None - divider_thickness: OptionalNumber = None - horizontal_margin: OptionalNumber = None - heading_text_style: Optional[TextStyle] = None - heading_row_color: ControlStateValue[ColorValue] = None - heading_row_height: OptionalNumber = None - data_row_cursor: ControlStateValue[MouseCursor] = None - decoration: Optional[BoxDecoration] = None - heading_row_alignment: Optional[MainAxisAlignment] = None - heading_cell_cursor: ControlStateValue[MouseCursor] = None - - -@deprecated_class( - "Use ElevatedButtonTheme, OutlinedButtonTheme, TextButtonTheme, FilledButtonTheme or IconButtonTheme instead.", - version="0.27.0", - delete_version="0.30.0", -) -@dataclass -class ButtonTheme: - button_color: Optional[ColorValue] = None - disabled_color: Optional[ColorValue] = None - hover_color: Optional[ColorValue] = None - focus_color: Optional[ColorValue] = None - highlight_color: Optional[ColorValue] = None - splash_color: Optional[ColorValue] = None - color_scheme: Optional[ColorScheme] = None - aligned_dropdown: Optional[bool] = None - height: OptionalNumber = None - min_width: OptionalNumber = None - shape: Optional[OutlinedBorder] = None - padding: Optional[PaddingValue] = None - - -@dataclass -class Theme: - color_scheme_seed: Optional[ColorValue] = None - primary_swatch: Optional[ColorValue] = None - font_family: Optional[str] = None - use_material3: Optional[bool] = None - appbar_theme: Optional[AppBarTheme] = None - badge_theme: Optional[BadgeTheme] = None - banner_theme: Optional[BannerTheme] = None - bottom_appbar_theme: Optional[BottomAppBarTheme] = None - bottom_sheet_theme: Optional[BottomSheetTheme] = None - button_theme: Optional[ButtonTheme] = None - card_theme: Optional[CardTheme] = None - checkbox_theme: Optional[CheckboxTheme] = None - chip_theme: Optional[ChipTheme] = None - color_scheme: Optional[ColorScheme] = None - data_table_theme: Optional[DataTableTheme] = None - date_picker_theme: Optional[DatePickerTheme] = None - dialog_theme: Optional[DialogTheme] = None - divider_theme: Optional[DividerTheme] = None - # dropdown_menu_theme: Optional[DropdownMenuTheme] = None - elevated_button_theme: Optional[ElevatedButtonTheme] = None - outlined_button_theme: Optional[OutlinedButtonTheme] = None - text_button_theme: Optional[TextButtonTheme] = None - filled_button_theme: Optional[FilledButtonTheme] = None - icon_button_theme: Optional[IconButtonTheme] = None - expansion_tile_theme: Optional[ExpansionTileTheme] = None - floating_action_button_theme: Optional[FloatingActionButtonTheme] = None - icon_theme: Optional[IconTheme] = None - list_tile_theme: Optional[ListTileTheme] = None - navigation_bar_theme: Optional[NavigationBarTheme] = None - navigation_drawer_theme: Optional[NavigationDrawerTheme] = None - navigation_rail_theme: Optional[NavigationRailTheme] = None - page_transitions: PageTransitionsTheme = field(default_factory=PageTransitionsTheme) - popup_menu_theme: Optional[PopupMenuTheme] = None - splash_color: Optional[ColorValue] = None - highlight_color: Optional[ColorValue] = None - hover_color: Optional[ColorValue] = None - focus_color: Optional[ColorValue] = None - unselected_control_color: Optional[ColorValue] = None - disabled_color: Optional[ColorValue] = None - canvas_color: Optional[ColorValue] = None - scaffold_bgcolor: Optional[ColorValue] = None - card_color: Optional[ColorValue] = None - divider_color: Optional[ColorValue] = None - dialog_bgcolor: Optional[ColorValue] = None - indicator_color: Optional[ColorValue] = None - hint_color: Optional[ColorValue] = None - shadow_color: Optional[ColorValue] = None - secondary_header_color: Optional[ColorValue] = None - primary_color: Optional[ColorValue] = None - primary_color_dark: Optional[ColorValue] = None - primary_color_light: Optional[ColorValue] = None - primary_text_theme: Optional[TextTheme] = None - progress_indicator_theme: Optional[ProgressIndicatorTheme] = None - radio_theme: Optional[RadioTheme] = None - scrollbar_theme: Optional[ScrollbarTheme] = None - search_bar_theme: Optional[SearchBarTheme] = None - search_view_theme: Optional[SearchViewTheme] = None - segmented_button_theme: Optional[SegmentedButtonTheme] = None - slider_theme: Optional[SliderTheme] = None - snackbar_theme: Optional[SnackBarTheme] = None - switch_theme: Optional[SwitchTheme] = None - system_overlay_style: SystemOverlayStyle = field(default_factory=SystemOverlayStyle) - tabs_theme: Optional[TabsTheme] = None - text_theme: Optional[TextTheme] = None - time_picker_theme: Optional[TimePickerTheme] = None - tooltip_theme: Optional[TooltipTheme] = None - visual_density: Optional[VisualDensity] = None - - def __post_init__(self): - if self.button_theme: - deprecated_property( - "button_theme", - "Use elevated_button_theme, outlined_button_theme, text_button_theme, filled_button_theme or icon_button_theme instead.", - version="0.27.0", - delete_version="0.30.0", - ) - if self.dialog_bgcolor: - deprecated_property( - "dialog_bgcolor", - "Use dialog_theme.bgcolor instead.", - version="0.27.0", - delete_version="0.30.0", - ) diff --git a/sdk/python/packages/flet/src/flet/core/time_picker.py b/sdk/python/packages/flet/src/flet/core/time_picker.py deleted file mode 100644 index ce48bef6e..000000000 --- a/sdk/python/packages/flet/src/flet/core/time_picker.py +++ /dev/null @@ -1,285 +0,0 @@ -from datetime import datetime, time -from enum import Enum -from typing import Any, Optional, Union - -from flet.core.control import Control, OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - OptionalControlEventCallable, - OptionalEventCallable, - Orientation, - ResponsiveNumber, -) - - -class TimePickerEntryMode(Enum): - DIAL = "dial" - INPUT = "input" - DIAL_ONLY = "dialOnly" - INPUT_ONLY = "inputOnly" - - -class TimePickerEntryModeChangeEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - self.entry_mode: Optional[TimePickerEntryMode] = TimePickerEntryMode(e.data) - - -class TimePicker(Control): - """ - A Material-style time picker dialog. - - It is added to [`page.overlay`](page#overlay) and can be opened by setting `open=True` or by calling `Page.open()` method. - - Depending on the `time_picker_entry_mode`, it will show either a Dial or an Input (hour and minute text fields) for picking a time. - - Example: - ``` - import flet as ft - - - def main(page: ft.Page): - page.horizontal_alignment = ft.CrossAxisAlignment.CENTER - - def handle_change(e): - page.add(ft.Text(f"TimePicker change: {time_picker.value}")) - - def handle_dismissal(e): - page.add(ft.Text(f"TimePicker dismissed: {time_picker.value}")) - - def handle_entry_mode_change(e): - page.add(ft.Text(f"TimePicker Entry mode changed to {e.entry_mode}")) - - time_picker = ft.TimePicker( - confirm_text="Confirm", - error_invalid_text="Time out of range", - help_text="Pick your time slot", - on_change=handle_change, - on_dismiss=handle_dismissal, - on_entry_mode_change=handle_entry_mode_change, - ) - - page.add( - ft.ElevatedButton( - "Pick time", - icon=ft.icons.TIME_TO_LEAVE, - on_click=lambda _: page.open(time_picker), - ) - ) - - - ft.app(main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/time_picker - """ - - def __init__( - self, - value: Optional[time] = datetime.now().time(), - open: bool = False, - time_picker_entry_mode: Optional[TimePickerEntryMode] = None, - hour_label_text: Optional[str] = None, - minute_label_text: Optional[str] = None, - help_text: Optional[str] = None, - cancel_text: Optional[str] = None, - confirm_text: Optional[str] = None, - error_invalid_text: Optional[str] = None, - orientation: Optional[Orientation] = None, - barrier_color: Optional[ColorValue] = None, - on_change: OptionalControlEventCallable = None, - on_dismiss: OptionalControlEventCallable = None, - on_entry_mode_change: OptionalEventCallable[ - TimePickerEntryModeChangeEvent - ] = None, - # - # Control - # - ref: Optional[Ref] = None, - expand: Optional[Union[bool, int]] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - tooltip: Optional[TooltipValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - Control.__init__( - self, - ref=ref, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - tooltip=tooltip, - visible=visible, - disabled=disabled, - data=data, - ) - - self.__on_entry_mode_change = EventHandler( - lambda e: TimePickerEntryModeChangeEvent(e) - ) - self._add_event_handler( - "entryModeChange", self.__on_entry_mode_change.get_handler() - ) - - self.value = value - self.help_text = help_text - self.cancel_text = cancel_text - self.confirm_text = confirm_text - self.error_invalid_text = error_invalid_text - self.hour_label_text = hour_label_text - self.minute_label_text = minute_label_text - self.time_picker_entry_mode = time_picker_entry_mode - self.orientation = orientation - self.on_change = on_change - self.on_dismiss = on_dismiss - self.open = open - self.on_entry_mode_change = on_entry_mode_change - self.barrier_color = barrier_color - - def _get_control_name(self): - return "timepicker" - - # open - @property - def open(self) -> bool: - return self._get_attr("open", data_type="bool", def_value=False) - - @open.setter - def open(self, value: Optional[bool]): - self._set_attr("open", value) - - # value - @property - def value(self) -> time: - v = self._get_attr("value") # format HH:MM - return time(*map(int, v.split(":"))) - - @value.setter - def value(self, value: time): - self._set_attr("value", value.strftime("%H:%M")) - - # hour_label_text - @property - def hour_label_text(self) -> Optional[str]: - return self._get_attr("hourLabelText", def_value=None) - - @hour_label_text.setter - def hour_label_text(self, value: Optional[str]): - self._set_attr("hourLabelText", value) - - # minute_label_text - @property - def minute_label_text(self) -> Optional[str]: - return self._get_attr("minuteLabelText", def_value=None) - - @minute_label_text.setter - def minute_label_text(self, value: Optional[str]): - self._set_attr("minuteLabelText", value) - - # help_text - @property - def help_text(self) -> Optional[str]: - return self._get_attr("helpText", def_value=None) - - @help_text.setter - def help_text(self, value: Optional[str]): - self._set_attr("helpText", value) - - # cancel_text - @property - def cancel_text(self) -> Optional[str]: - return self._get_attr("cancelText", def_value=None) - - @cancel_text.setter - def cancel_text(self, value: Optional[str]): - self._set_attr("cancelText", value) - - # confirm_text - @property - def confirm_text(self) -> Optional[str]: - return self._get_attr("confirmText", def_value=None) - - @confirm_text.setter - def confirm_text(self, value: Optional[str]): - self._set_attr("confirmText", value) - - # error_invalid_text - @property - def error_invalid_text(self) -> Optional[str]: - return self._get_attr("errorInvalidText", def_value=None) - - @error_invalid_text.setter - def error_invalid_text(self, value: Optional[str]): - self._set_attr("errorInvalidText", value) - - # time_picker_entry_mode - @property - def time_picker_entry_mode(self) -> Optional[TimePickerEntryMode]: - return self.__time_picker_entry_mode - - @time_picker_entry_mode.setter - def time_picker_entry_mode(self, value: Optional[TimePickerEntryMode]): - self.__time_picker_entry_mode = value - self._set_enum_attr("timePickerEntryMode", value, TimePickerEntryMode) - - # orientation - @property - def orientation(self) -> Optional[Orientation]: - return self.__orientation - - @orientation.setter - def orientation(self, value: Optional[Orientation]): - self.__orientation = value - self._set_enum_attr("orientation", value, Orientation) - - # on_change - @property - def on_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("change") - - @on_change.setter - def on_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("change", handler) - - # on_dismiss - @property - def on_dismiss(self) -> OptionalControlEventCallable: - return self._get_event_handler("dismiss") - - @on_dismiss.setter - def on_dismiss(self, handler: OptionalControlEventCallable): - self._add_event_handler("dismiss", handler) - - # on_entry_mode_change - @property - def on_entry_mode_change( - self, - ) -> OptionalEventCallable[TimePickerEntryModeChangeEvent]: - return self.__on_entry_mode_change.handler - - @on_entry_mode_change.setter - def on_entry_mode_change( - self, handler: OptionalEventCallable[TimePickerEntryModeChangeEvent] - ): - self.__on_entry_mode_change.handler = handler - - # barrier_color - @property - def barrier_color(self) -> Optional[ColorValue]: - return self.__barrier_color - - @barrier_color.setter - def barrier_color(self, value: Optional[ColorValue]): - self.__barrier_color = value - self._set_enum_attr("barrierColor", value, ColorEnums) diff --git a/sdk/python/packages/flet/src/flet/core/tooltip.py b/sdk/python/packages/flet/src/flet/core/tooltip.py deleted file mode 100644 index 417ddea71..000000000 --- a/sdk/python/packages/flet/src/flet/core/tooltip.py +++ /dev/null @@ -1,55 +0,0 @@ -from dataclasses import dataclass -from typing import List, Optional, Union - -from flet.core.border import Border -from flet.core.box import BoxShadow, BoxShape, DecorationImage -from flet.core.gradients import Gradient -from flet.core.text_style import TextStyle -from flet.core.types import ( - BlendMode, - BorderRadiusValue, - ColorValue, - DurationValue, - MarginValue, - OptionalNumber, - PaddingValue, - TextAlign, -) - - -class TooltipTriggerMode: - MANUAL = "manual" - TAP = "tap" - LONG_PRESS = "long_press" - - -@dataclass -class Tooltip: - """Tooltips provide text labels which help explain the function of a button or other user interface action.""" - - message: str - enable_feedback: Optional[bool] = None - height: OptionalNumber = None - vertical_offset: OptionalNumber = None - margin: Optional[MarginValue] = None - padding: Optional[PaddingValue] = None - bgcolor: Optional[ColorValue] = None - image: Optional[DecorationImage] = None - shadow: Optional[List[BoxShadow]] = None - blend_mode: Optional[BlendMode] = None - gradient: Optional[Gradient] = None - border: Optional[Border] = None - border_radius: Optional[BorderRadiusValue] = None - shape: Optional[BoxShape] = None - text_style: Optional[TextStyle] = None - text_align: Optional[TextAlign] = None - prefer_below: Optional[bool] = None - show_duration: Optional[DurationValue] = None - wait_duration: Optional[DurationValue] = None - exit_duration: Optional[DurationValue] = None - enable_tap_to_dismiss: Optional[bool] = None - exclude_from_semantics: Optional[bool] = None - trigger_mode: Optional[TooltipTriggerMode] = None - - -TooltipValue = Union[str, "Tooltip"] diff --git a/sdk/python/packages/flet/src/flet/core/transform.py b/sdk/python/packages/flet/src/flet/core/transform.py deleted file mode 100644 index 6eb08c2ae..000000000 --- a/sdk/python/packages/flet/src/flet/core/transform.py +++ /dev/null @@ -1,24 +0,0 @@ -from dataclasses import dataclass, field -from typing import Optional - -from flet.core.alignment import Alignment - - -@dataclass -class Scale: - scale: Optional[float] = field(default=None) - scale_x: Optional[float] = field(default=None) - scale_y: Optional[float] = field(default=None) - alignment: Optional[Alignment] = field(default=None) - - -@dataclass -class Rotate: - angle: float - alignment: Optional[Alignment] = field(default=None) - - -@dataclass -class Offset: - x: float - y: float diff --git a/sdk/python/packages/flet/src/flet/core/transparent_pointer.py b/sdk/python/packages/flet/src/flet/core/transparent_pointer.py deleted file mode 100644 index 0c7dcd798..000000000 --- a/sdk/python/packages/flet/src/flet/core/transparent_pointer.py +++ /dev/null @@ -1,99 +0,0 @@ -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - OffsetValue, - OptionalControlEventCallable, - RotateValue, - ScaleValue, -) - - -class TransparentPointer(ConstrainedControl): - def __init__( - self, - content: Optional[Control] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.content = content - - def _get_control_name(self): - return "transparentpointer" - - def _get_children(self): - if self.__content is not None: - self.__content._set_attr_internal("n", "content") - return [self.__content] - return [] - - # content - @property - def content(self) -> Optional[Control]: - return self.__content - - @content.setter - def content(self, value: Optional[Control]): - self.__content = value diff --git a/sdk/python/packages/flet/src/flet/core/vertical_divider.py b/sdk/python/packages/flet/src/flet/core/vertical_divider.py deleted file mode 100644 index f377146f8..000000000 --- a/sdk/python/packages/flet/src/flet/core/vertical_divider.py +++ /dev/null @@ -1,130 +0,0 @@ -from typing import Any, Optional - -from flet.core.control import Control, OptionalNumber -from flet.core.ref import Ref -from flet.core.types import ColorEnums, ColorValue - - -class VerticalDivider(Control): - """ - A thin vertical line, with padding on either side. - - In the material design language, this represents a divider. - - Example: - - ``` - import flet as ft - - def main(page: ft.Page): - - page.add( - ft.Row( - [ - ft.Container( - bgcolor=ft.colors.ORANGE_300, - alignment=ft.alignment.center, - expand=True, - ), - ft.VerticalDivider(), - ft.Container( - bgcolor=ft.colors.BROWN_400, - alignment=ft.alignment.center, - expand=True, - ), - ], - spacing=0, - expand=True, - ) - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/verticaldivider - """ - - def __init__( - self, - width: OptionalNumber = None, - thickness: OptionalNumber = None, - color: Optional[ColorValue] = None, - leading_indent: OptionalNumber = None, - trailing_indent: OptionalNumber = None, - # - # Control - # - ref: Optional[Ref] = None, - opacity: OptionalNumber = None, - visible: Optional[bool] = None, - data: Any = None, - ): - - Control.__init__( - self, - ref=ref, - opacity=opacity, - visible=visible, - data=data, - ) - - self.width = width - self.thickness = thickness - self.color = color - self.leading_indent = leading_indent - self.trailing_indent = trailing_indent - - def _get_control_name(self): - return "verticaldivider" - - # width - @property - def width(self) -> OptionalNumber: - return self._get_attr("width", data_type="float") - - @width.setter - def width(self, value: OptionalNumber): - assert value is None or value >= 0, "width cannot be negative" - self._set_attr("width", value) - - # thickness - @property - def thickness(self) -> OptionalNumber: - return self._get_attr("thickness", data_type="float") - - @thickness.setter - def thickness(self, value: OptionalNumber): - assert value is None or value >= 0, "thickness cannot be negative" - self._set_attr("thickness", value) - - # color - @property - def color(self) -> Optional[ColorValue]: - return self.__color - - @color.setter - def color(self, value: Optional[ColorValue]): - self.__color = value - self._set_enum_attr("color", value, ColorEnums) - - # leading_indent - @property - def leading_indent(self) -> OptionalNumber: - return self._get_attr("leadingIndent", data_type="float") - - @leading_indent.setter - def leading_indent(self, value: OptionalNumber): - assert value is None or value >= 0, "leading_indent cannot be negative" - self._set_attr("leadingIndent", value) - - # trailing_indent - @property - def trailing_indent(self) -> OptionalNumber: - return self._get_attr("trailingIndent", data_type="float") - - @trailing_indent.setter - def trailing_indent(self, value: OptionalNumber): - assert value is None or value >= 0, "trailing_indent cannot be negative" - self._set_attr("trailingIndent", value) diff --git a/sdk/python/packages/flet/src/flet/core/video.py b/sdk/python/packages/flet/src/flet/core/video.py deleted file mode 100644 index 135ad8b3e..000000000 --- a/sdk/python/packages/flet/src/flet/core/video.py +++ /dev/null @@ -1,558 +0,0 @@ -import dataclasses -from enum import Enum -from typing import Any, Dict, List, Optional, Union, cast - -from flet.core.alignment import Alignment -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.box import FilterQuality -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.ref import Ref -from flet.core.text_style import TextStyle -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - ImageFit, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - PaddingValue, - ResponsiveNumber, - RotateValue, - ScaleValue, - TextAlign, -) -from flet.utils import deprecated - - -class PlaylistMode(Enum): - NONE = "none" - SINGLE = "single" - LOOP = "loop" - - -@dataclasses.dataclass -class VideoMedia: - resource: Optional[str] = dataclasses.field(default=None) - http_headers: Optional[Dict[str, str]] = dataclasses.field(default=None) - extras: Optional[Dict[str, str]] = dataclasses.field(default=None) - - -@dataclasses.dataclass -class VideoConfiguration: - output_driver: Optional[str] = dataclasses.field(default=None) - hardware_decoding_api: Optional[str] = dataclasses.field(default=None) - enable_hardware_acceleration: Optional[bool] = dataclasses.field(default=None) - - -@dataclasses.dataclass -class VideoSubtitleConfiguration: - src: Optional[str] = dataclasses.field(default=None) - title: Optional[str] = dataclasses.field(default=None) - language: Optional[str] = dataclasses.field(default=None) - text_style: Optional[TextStyle] = dataclasses.field(default=None) - text_scale_factor: Optional[OptionalNumber] = dataclasses.field(default=None) - text_align: Optional[TextAlign] = dataclasses.field(default=None) - padding: Optional[PaddingValue] = dataclasses.field(default=None) - visible: Optional[bool] = dataclasses.field(default=None) - - -@deprecated( - reason="Video control has been moved to a separate Python package: https://pypi.org/project/flet-video. " - + "Read more about this change in Flet blog: https://flet.dev/blog/flet-v-0-26-release-announcement", - version="0.26.0", - delete_version="0.29.0", -) -class Video(ConstrainedControl): - """ - A control that displays a video from a playlist. - - ----- - - Online docs: https://flet.dev/docs/controls/video - """ - - def __init__( - self, - playlist: Optional[List[VideoMedia]] = None, - title: Optional[str] = None, - fit: Optional[ImageFit] = None, - fill_color: Optional[ColorValue] = None, - wakelock: Optional[bool] = None, - autoplay: Optional[bool] = None, - show_controls: Optional[bool] = None, - muted: Optional[bool] = None, - playlist_mode: Optional[PlaylistMode] = None, - shuffle_playlist: Optional[bool] = None, - volume: OptionalNumber = None, - playback_rate: OptionalNumber = None, - alignment: Optional[Alignment] = None, - filter_quality: Optional[FilterQuality] = None, - pause_upon_entering_background_mode: Optional[bool] = None, - resume_upon_entering_foreground_mode: Optional[bool] = None, - aspect_ratio: OptionalNumber = None, - pitch: OptionalNumber = None, - configuration: Optional[VideoConfiguration] = None, - subtitle_configuration: Optional[VideoSubtitleConfiguration] = None, - on_loaded: OptionalControlEventCallable = None, - on_enter_fullscreen: OptionalControlEventCallable = None, - on_exit_fullscreen: OptionalControlEventCallable = None, - on_error: OptionalControlEventCallable = None, - on_completed: OptionalControlEventCallable = None, - on_track_changed: OptionalControlEventCallable = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - - self.__playlist = playlist or [] - self.subtitle_configuration = subtitle_configuration - self.configuration = configuration - self.fit = fit - self.pitch = pitch - self.fill_color = fill_color - self.volume = volume - self.playback_rate = playback_rate - self.alignment = alignment - self.wakelock = wakelock - self.autoplay = autoplay - self.show_controls = show_controls - self.shuffle_playlist = shuffle_playlist - self.muted = muted - self.title = title - self.filter_quality = filter_quality - self.playlist_mode = playlist_mode - self.pause_upon_entering_background_mode = pause_upon_entering_background_mode - self.resume_upon_entering_foreground_mode = resume_upon_entering_foreground_mode - self.on_enter_fullscreen = on_enter_fullscreen - self.on_exit_fullscreen = on_exit_fullscreen - self.on_loaded = on_loaded - self.on_error = on_error - self.on_completed = on_completed - self.on_track_changed = on_track_changed - - def _get_control_name(self): - return "video" - - def before_update(self): - super().before_update() - self._set_attr_json("alignment", self.__alignment) - self._set_attr_json("playlist", self.__playlist if self.__playlist else None) - if isinstance(self.__subtitle_configuration, VideoSubtitleConfiguration): - self._set_attr_json("subtitleConfiguration", self.__subtitle_configuration) - - if isinstance(self.__configuration, VideoConfiguration): - self._set_attr_json("configuration", self.__configuration) - - def play(self): - self.invoke_method("play") - - def pause(self): - self.invoke_method("pause") - - def play_or_pause(self): - self.invoke_method("play_or_pause") - - def stop(self): - self.invoke_method("stop") - - def next(self): - self.invoke_method("next") - - def previous(self): - self.invoke_method("previous") - - def seek(self, position_milliseconds: int): - self.invoke_method("seek", {"position": str(position_milliseconds)}) - - def jump_to(self, media_index: int): - assert self.__playlist[media_index], "media_index is out of range" - if media_index < 0: - # dart doesn't support negative indexes - media_index = len(self.__playlist) + media_index - self.invoke_method("jump_to", {"media_index": str(media_index)}) - - def playlist_add(self, media: VideoMedia): - assert media.resource, "media has no resource" - self.invoke_method( - "playlist_add", - { - "resource": media.resource, - "http_headers": str(media.http_headers or {}), - "extras": str(media.extras or {}), - }, - ) - self.__playlist.append(media) - - def playlist_remove(self, media_index: int): - assert self.__playlist[media_index], "index out of range" - self.invoke_method("playlist_remove", {"media_index": str(media_index)}) - self.__playlist.pop(media_index) - - def is_playing(self, wait_timeout: Optional[float] = 5) -> bool: - playing = self.invoke_method( - "is_playing", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return playing == "true" - - async def is_playing_async(self, wait_timeout: Optional[float] = 5) -> bool: - playing = await self.invoke_method_async( - "is_playing", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return playing == "true" - - def is_completed(self, wait_timeout: Optional[float] = 5) -> bool: - completed = self.invoke_method( - "is_completed", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return completed == "true" - - async def is_completed_async(self, wait_timeout: Optional[float] = 5) -> bool: - completed = await self.invoke_method_async( - "is_completed", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return completed == "true" - - def get_duration(self, wait_timeout: Optional[float] = 5) -> Optional[int]: - sr = self.invoke_method( - "get_duration", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return int(sr) if sr else None - - async def get_duration_async( - self, wait_timeout: Optional[float] = 5 - ) -> Optional[int]: - sr = await self.invoke_method_async( - "get_duration", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return int(sr) if sr else None - - def get_current_position(self, wait_timeout: Optional[float] = 5) -> Optional[int]: - sr = self.invoke_method( - "get_current_position", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return int(sr) if sr else None - - async def get_current_position_async( - self, wait_timeout: Optional[float] = 5 - ) -> Optional[int]: - sr = await self.invoke_method_async( - "get_current_position", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - return int(sr) if sr else None - - # playlist - @property - def playlist(self) -> Optional[List[VideoMedia]]: - return self.__playlist - - # fit - @property - def fit(self) -> Optional[ImageFit]: - return self.__fit - - @fit.setter - def fit(self, value: Optional[ImageFit]): - self.__fit = value - self._set_attr("fit", value.value if isinstance(value, ImageFit) else value) - - # subtitle_configuration - @property - def subtitle_configuration(self) -> Optional[VideoSubtitleConfiguration]: - return self.__subtitle_configuration - - @subtitle_configuration.setter - def subtitle_configuration(self, value: Optional[VideoSubtitleConfiguration]): - self.__subtitle_configuration = value - - # configuration - @property - def configuration(self) -> Optional[VideoConfiguration]: - return self.__configuration - - @configuration.setter - def configuration(self, value: Optional[VideoConfiguration]): - self.__configuration = value - - # fill_color - @property - def fill_color(self) -> Optional[ColorValue]: - return self.__fill_color - - @fill_color.setter - def fill_color(self, value: Optional[ColorValue]): - self.__fill_color = value - self._set_enum_attr("fillColor", value, ColorEnums) - - # wakelock - @property - def wakelock(self) -> bool: - return self._get_attr("wakelock", data_type="bool", def_value=True) - - @wakelock.setter - def wakelock(self, value: Optional[bool]): - self._set_attr("wakelock", value) - - # autoplay - @property - def autoplay(self) -> bool: - return self._get_attr("autoPlay", data_type="bool", def_value=False) - - @autoplay.setter - def autoplay(self, value: Optional[bool]): - self._set_attr("autoPlay", value) - - # muted - @property - def muted(self) -> bool: - return self._get_attr("muted", data_type="bool", def_value=False) - - @muted.setter - def muted(self, value: Optional[bool]): - self._set_attr("muted", value) - - # shuffle_playlist - @property - def shuffle_playlist(self) -> bool: - return self._get_attr("shufflePlaylist", data_type="bool", def_value=False) - - @shuffle_playlist.setter - def shuffle_playlist(self, value: Optional[bool]): - self._set_attr("shufflePlaylist", value) - - # show_controls - @property - def show_controls(self) -> bool: - return self._get_attr("showControls", data_type="bool", def_value=True) - - @show_controls.setter - def show_controls(self, value: Optional[bool]): - self._set_attr("showControls", value) - - # pitch - @property - def pitch(self) -> OptionalNumber: - return self._get_attr("pitch", data_type="float") - - @pitch.setter - def pitch(self, value: OptionalNumber): - self._set_attr("pitch", value) - - # volume - @property - def volume(self) -> OptionalNumber: - return self._get_attr("volume", data_type="float") - - @volume.setter - def volume(self, value: OptionalNumber): - assert value is None or 0 <= value <= 100, "volume must be between 0 and 100" - self._set_attr("volume", value) - - # playback_rate - @property - def playback_rate(self) -> OptionalNumber: - return self._get_attr("playbackRate", data_type="float") - - @playback_rate.setter - def playback_rate(self, value: OptionalNumber): - self._set_attr("playbackRate", value) - - # title - @property - def title(self) -> Optional[str]: - return self._get_attr("title") - - @title.setter - def title(self, value: Optional[str]): - self._set_attr("title", value) - - # pause_upon_entering_background_mode - @property - def pause_upon_entering_background_mode(self) -> bool: - return cast( - bool, - self._get_attr( - "pauseUponEnteringBackgroundMode", data_type="bool", def_value=True - ), - ) - - @pause_upon_entering_background_mode.setter - def pause_upon_entering_background_mode(self, value: Optional[bool]): - self._set_attr("pauseUponEnteringBackgroundMode", value) - - # resume_upon_entering_foreground_mode - @property - def resume_upon_entering_foreground_mode(self) -> bool: - return cast( - bool, - self._get_attr( - "resumeUponEnteringForegroundMode", data_type="bool", def_value=False - ), - ) - - @resume_upon_entering_foreground_mode.setter - def resume_upon_entering_foreground_mode(self, value: Optional[bool]): - self._set_attr("resumeUponEnteringForegroundMode", value) - - # alignment - @property - def alignment(self) -> Optional[Alignment]: - return self.__alignment - - @alignment.setter - def alignment(self, value: Optional[Alignment]): - self.__alignment = value - - # filter_quality - @property - def filter_quality(self) -> Optional[FilterQuality]: - return self.__filter_quality - - @filter_quality.setter - def filter_quality(self, value: Optional[FilterQuality]): - self.__filter_quality = value - self._set_enum_attr("filterQuality", value, FilterQuality) - - # playlist_mode - @property - def playlist_mode(self) -> Optional[PlaylistMode]: - return self.__playlist_mode - - @playlist_mode.setter - def playlist_mode(self, value: Optional[PlaylistMode]): - self.__playlist_mode = value - self._set_enum_attr("playlistMode", value, PlaylistMode) - - # on_enter_fullscreen - @property - def on_enter_fullscreen(self): - return self._get_event_handler("enter_fullscreen") - - @on_enter_fullscreen.setter - def on_enter_fullscreen(self, handler: OptionalControlEventCallable): - self._add_event_handler("enter_fullscreen", handler) - self._set_attr("onEnterFullscreen", True if handler is not None else None) - - # on_exit_fullscreen - @property - def on_exit_fullscreen(self) -> OptionalControlEventCallable: - return self._get_event_handler("exit_fullscreen") - - @on_exit_fullscreen.setter - def on_exit_fullscreen(self, handler: OptionalControlEventCallable): - self._add_event_handler("exit_fullscreen", handler) - self._set_attr("onExitFullscreen", True if handler is not None else None) - - # on_loaded - @property - def on_loaded(self) -> OptionalControlEventCallable: - return self._get_event_handler("loaded") - - @on_loaded.setter - def on_loaded(self, handler: OptionalControlEventCallable): - self._set_attr("onLoaded", True if handler is not None else None) - self._add_event_handler("loaded", handler) - - # on_error - @property - def on_error(self) -> OptionalControlEventCallable: - return self._get_event_handler("error") - - @on_error.setter - def on_error(self, handler: OptionalControlEventCallable): - self._set_attr("onError", True if handler is not None else None) - self._add_event_handler("error", handler) - - # on_completed - @property - def on_completed(self) -> OptionalControlEventCallable: - return self._get_event_handler("completed") - - @on_completed.setter - def on_completed(self, handler: OptionalControlEventCallable): - self._set_attr("onCompleted", True if handler is not None else None) - self._add_event_handler("completed", handler) - - # on_track_changed - @property - def on_track_changed(self) -> OptionalControlEventCallable: - return self._get_event_handler("track_changed") - - @on_track_changed.setter - def on_track_changed(self, handler: OptionalControlEventCallable): - self._set_attr("onTrackChanged", True if handler is not None else None) - self._add_event_handler("track_changed", handler) diff --git a/sdk/python/packages/flet/src/flet/core/view.py b/sdk/python/packages/flet/src/flet/core/view.py deleted file mode 100644 index 4fdf6fff2..000000000 --- a/sdk/python/packages/flet/src/flet/core/view.py +++ /dev/null @@ -1,335 +0,0 @@ -from typing import List, Optional, Sequence, Union - -from flet.core.adaptive_control import AdaptiveControl -from flet.core.app_bar import AppBar -from flet.core.bottom_app_bar import BottomAppBar -from flet.core.box import BoxDecoration -from flet.core.control import Control, OptionalNumber -from flet.core.cupertino_app_bar import CupertinoAppBar -from flet.core.cupertino_navigation_bar import CupertinoNavigationBar -from flet.core.floating_action_button import FloatingActionButton -from flet.core.navigation_bar import NavigationBar -from flet.core.navigation_drawer import NavigationDrawer -from flet.core.scrollable_control import OnScrollEvent, ScrollableControl -from flet.core.types import ( - ColorEnums, - ColorValue, - CrossAxisAlignment, - FloatingActionButtonLocation, - MainAxisAlignment, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - PaddingValue, - ScrollMode, -) - - -class View(ScrollableControl, AdaptiveControl): - """ - View is the top most container for all other controls. - - A root view is automatically created when a new user session started. From layout perspective the View represents a `Column`(https://flet.dev/docs/controls/column/) control, so it has a similar behavior and shares same properties. - - ----- - - Online docs: https://flet.dev/docs/controls/view - """ - - def __init__( - self, - route: Optional[str] = None, - controls: Optional[Sequence[Control]] = None, - appbar: Union[AppBar, CupertinoAppBar, None] = None, - bottom_appbar: Optional[BottomAppBar] = None, - floating_action_button: Optional[FloatingActionButton] = None, - floating_action_button_location: Union[ - FloatingActionButtonLocation, OffsetValue - ] = None, - navigation_bar: Union[NavigationBar, CupertinoNavigationBar, None] = None, - drawer: Optional[NavigationDrawer] = None, - end_drawer: Optional[NavigationDrawer] = None, - vertical_alignment: Optional[MainAxisAlignment] = None, - horizontal_alignment: Optional[CrossAxisAlignment] = None, - spacing: OptionalNumber = None, - padding: Optional[PaddingValue] = None, - bgcolor: Optional[ColorValue] = None, - decoration: Optional[BoxDecoration] = None, - foreground_decoration: Optional[BoxDecoration] = None, - can_pop: Optional[bool] = None, - on_confirm_pop: OptionalControlEventCallable = None, - # - # ScrollableControl - # - scroll: Optional[ScrollMode] = None, - auto_scroll: Optional[bool] = None, - fullscreen_dialog: Optional[bool] = None, - on_scroll_interval: OptionalNumber = None, - on_scroll: OptionalEventCallable[OnScrollEvent] = None, - # - # AdaptiveControl - # - adaptive: Optional[bool] = None, - ): - Control.__init__(self) - - ScrollableControl.__init__( - self, - scroll=scroll, - auto_scroll=auto_scroll, - on_scroll_interval=on_scroll_interval, - on_scroll=on_scroll, - ) - - AdaptiveControl.__init__(self, adaptive=adaptive) - - self.controls = controls - self.route = route - self.appbar = appbar - self.bottom_appbar = bottom_appbar - self.navigation_bar = navigation_bar - self.drawer = drawer - self.end_drawer = end_drawer - self.floating_action_button = floating_action_button - self.floating_action_button_location = floating_action_button_location - self.vertical_alignment = vertical_alignment - self.horizontal_alignment = horizontal_alignment - self.spacing = spacing - self.padding = padding - self.bgcolor = bgcolor - self.scroll = scroll - self.auto_scroll = auto_scroll - self.fullscreen_dialog = fullscreen_dialog - self.decoration = decoration - self.foreground_decoration = foreground_decoration - self.can_pop = can_pop - self.on_confirm_pop = on_confirm_pop - - def _get_control_name(self): - return "view" - - def before_update(self): - super().before_update() - self._set_attr_json("padding", self.__padding) - if not isinstance( - self.__floating_action_button_location, (FloatingActionButtonLocation, str) - ): - self._set_attr_json( - "floatingActionButtonLocation", self.__floating_action_button_location - ) - self._set_attr_json("decoration", self.__decoration) - self._set_attr_json("foregroundDecoration", self.__foreground_decoration) - - def _get_children(self): - children = [] - if self.__appbar: - children.append(self.__appbar) - if self.__bottom_appbar: - children.append(self.__bottom_appbar) - if self.__fab: - self.__fab._set_attr_internal("n", "fab") - children.append(self.__fab) - if self.__navigation_bar: - children.append(self.__navigation_bar) - if self.__drawer: - self.__drawer._set_attr_internal("n", "drawer_start") - children.append(self.__drawer) - if self.__end_drawer: - self.__end_drawer._set_attr_internal("n", "drawer_end") - children.append(self.__end_drawer) - return children + self.__controls - - def confirm_pop(self, shouldPop: bool): - self.invoke_method("confirm_pop", {"shouldPop": str(shouldPop).lower()}) - - # route - @property - def route(self): - return self._get_attr("route") - - @route.setter - def route(self, value): - self._set_attr("route", value) - - # controls - @property - def controls(self) -> List[Control]: - return self.__controls - - @controls.setter - def controls(self, value: Optional[Sequence[Control]]): - self.__controls = list(value) if value is not None else [] - - # appbar - @property - def appbar(self) -> Union[AppBar, CupertinoAppBar, None]: - return self.__appbar - - @appbar.setter - def appbar(self, value: Union[AppBar, CupertinoAppBar, None]): - self.__appbar = value - - # bottom_appbar - @property - def bottom_appbar(self) -> Optional[BottomAppBar]: - return self.__bottom_appbar - - @bottom_appbar.setter - def bottom_appbar(self, value: Optional[BottomAppBar]): - self.__bottom_appbar = value - - # floating_action_button - @property - def floating_action_button(self) -> Optional[FloatingActionButton]: - return self.__fab - - @floating_action_button.setter - def floating_action_button(self, value: Optional[FloatingActionButton]): - self.__fab = value - - # floating_action_button_location - @property - def floating_action_button_location( - self, - ) -> Union[FloatingActionButtonLocation, OffsetValue]: - return self.__floating_action_button_location - - @floating_action_button_location.setter - def floating_action_button_location( - self, value: Union[FloatingActionButtonLocation, OffsetValue] - ): - self.__floating_action_button_location = value - if isinstance(value, (FloatingActionButtonLocation, str)): - self._set_attr( - "floatingActionButtonLocation", - ( - value.value - if isinstance(value, FloatingActionButtonLocation) - else value - ), - ) - - # navigation_bar - @property - def navigation_bar(self) -> Union[NavigationBar, CupertinoNavigationBar, None]: - return self.__navigation_bar - - @navigation_bar.setter - def navigation_bar(self, value: Union[NavigationBar, CupertinoNavigationBar, None]): - self.__navigation_bar = value - - # drawer - @property - def drawer(self) -> Optional[NavigationDrawer]: - return self.__drawer - - @drawer.setter - def drawer(self, value: Optional[NavigationDrawer]): - self.__drawer = value - - # end_drawer - @property - def end_drawer(self) -> Optional[NavigationDrawer]: - return self.__end_drawer - - @end_drawer.setter - def end_drawer(self, value: Optional[NavigationDrawer]): - self.__end_drawer = value - - # horizontal_alignment - @property - def horizontal_alignment(self) -> CrossAxisAlignment: - return self.__horizontal_alignment - - @horizontal_alignment.setter - def horizontal_alignment(self, value: CrossAxisAlignment): - self.__horizontal_alignment = value - self._set_enum_attr("horizontalAlignment", value, CrossAxisAlignment) - - # vertical_alignment - @property - def vertical_alignment(self) -> MainAxisAlignment: - return self.__vertical_alignment - - @vertical_alignment.setter - def vertical_alignment(self, value: MainAxisAlignment): - self.__vertical_alignment = value - self._set_enum_attr("verticalAlignment", value, MainAxisAlignment) - - # spacing - @property - def spacing(self) -> OptionalNumber: - return self._get_attr("spacing", data_type="float") - - @spacing.setter - def spacing(self, value: OptionalNumber): - self._set_attr("spacing", value) - - # padding - @property - def padding(self) -> Optional[PaddingValue]: - return self.__padding - - @padding.setter - def padding(self, value: Optional[PaddingValue]): - self.__padding = value - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # fullscreen_dialog - @property - def fullscreen_dialog(self) -> bool: - return self._get_attr("fullscreenDialog", data_type="bool", def_value=False) - - @fullscreen_dialog.setter - def fullscreen_dialog(self, value: Optional[bool]): - self._set_attr("fullscreenDialog", value) - - # foreground_decoration - @property - def foreground_decoration(self) -> Optional[BoxDecoration]: - return self.__foreground_decoration - - @foreground_decoration.setter - def foreground_decoration(self, value: Optional[BoxDecoration]): - self.__foreground_decoration = value - - # decoration - @property - def decoration(self) -> Optional[BoxDecoration]: - return self.__decoration - - @decoration.setter - def decoration(self, value: Optional[BoxDecoration]): - self.__decoration = value - - # can_pop - @property - def can_pop(self) -> Optional[bool]: - return self._get_attr("canPop", data_type="bool") - - @can_pop.setter - def can_pop(self, value: Optional[bool]): - self._set_attr("canPop", value) - - # on_confirm_pop - @property - def on_confirm_pop(self): - return self._get_event_handler("confirm_pop") - - @on_confirm_pop.setter - def on_confirm_pop(self, handler: OptionalControlEventCallable): - self._add_event_handler("confirm_pop", handler) - self._set_attr("onConfirmPop", True if handler is not None else None) - - # Magic methods - def __contains__(self, item: Control) -> bool: - return item in self.__controls diff --git a/sdk/python/packages/flet/src/flet/core/webview.py b/sdk/python/packages/flet/src/flet/core/webview.py deleted file mode 100644 index 95b81fec0..000000000 --- a/sdk/python/packages/flet/src/flet/core/webview.py +++ /dev/null @@ -1,473 +0,0 @@ -import json -from enum import Enum -from typing import Any, Optional, Union - -from flet.core.animation import AnimationValue -from flet.core.badge import BadgeValue -from flet.core.constrained_control import ConstrainedControl -from flet.core.control import OptionalNumber -from flet.core.control_event import ControlEvent -from flet.core.event_handler import EventHandler -from flet.core.exceptions import FletUnsupportedPlatformException -from flet.core.ref import Ref -from flet.core.tooltip import TooltipValue -from flet.core.types import ( - ColorEnums, - ColorValue, - OffsetValue, - OptionalControlEventCallable, - OptionalEventCallable, - PagePlatform, - ResponsiveNumber, - RotateValue, - ScaleValue, -) -from flet.utils import deprecated - - -class WebviewRequestMethod(Enum): - GET = "get" - POST = "post" - - -class WebviewLogLevelSeverity(Enum): - ERROR = "error" - WARNING = "warning" - DEBUG = "debug" - INFO = "info" - LOG = "log" - - -class WebviewScrollEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.x: float = d.get("x", 0) - self.y: float = d.get("y", 0) - - -class WebviewConsoleMessageEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.message: str = d.get("message") - self.severity_level: WebviewLogLevelSeverity = WebviewLogLevelSeverity( - d.get("level") - ) - - -class WebviewJavaScriptEvent(ControlEvent): - def __init__(self, e: ControlEvent): - super().__init__(e.target, e.name, e.data, e.control, e.page) - d = json.loads(e.data) - self.message: str = d.get("message") - self.url: str = d.get("url") - - -@deprecated( - reason="WebView control has been moved to a separate Python package: https://pypi.org/project/flet-webview. " - + "Read more about this change in Flet blog: https://flet.dev/blog/flet-v-0-26-release-announcement", - version="0.26.0", - delete_version="0.29.0", -) -class WebView(ConstrainedControl): - """ - Easily load webpages while allowing user interaction. - - The `WebView` control is designed exclusively for iOS and Android platforms. - - ## Examples - A simple webview implementation using this class could be like: - - ```python - import flet - - def main(page: flet.Page): - wv = flet.WebView( - "https://flet.dev", - expand=True, - on_page_started=lambda _: print("Page started"), - on_page_ended=lambda _: print("Page ended"), - on_web_resource_error=lambda e: print("Page error:", e.data), - ) - page.add(wv) - - flet.app(main) - ``` - - ## Properties - - ### `url` - - Start the webview by loading the `url` value. - - ### `javascript_enabled` - - Enable or disable the javascript execution of the page. Note that disabling the javascript execution of the page may result unexpected webpage behaviour. - - ### `prevent_link` - - Specify a link to prevent it from downloading. - - ### `bgcolor` - - Set the background color of the webview. - - ## Events - - ### `on_page_started` - - Fires soon as the first loading process of the webpage is started. - - ### `on_page_ended` - - Fires when all the webpage loading processes are ended. - - ### `on_web_resource_error` - - Fires when there is error with loading a webpage resource. - - View docs: [WebView](https://flet.dev/docs/controls/webview) - """ - - def __init__( - self, - url: str, - enable_javascript: Optional[bool] = None, - prevent_link: Optional[str] = None, - bgcolor: Optional[ColorValue] = None, - on_page_started: OptionalControlEventCallable = None, - on_page_ended: OptionalControlEventCallable = None, - on_web_resource_error: OptionalControlEventCallable = None, - on_progress: OptionalControlEventCallable = None, - on_url_change: OptionalControlEventCallable = None, - on_scroll: OptionalEventCallable[WebviewScrollEvent] = None, - on_console_message: OptionalEventCallable[WebviewConsoleMessageEvent] = None, - on_javascript_alert_dialog: OptionalEventCallable[ - WebviewJavaScriptEvent - ] = None, - # - # ConstrainedControl - # - ref: Optional[Ref] = None, - key: Optional[str] = None, - width: OptionalNumber = None, - height: OptionalNumber = None, - left: OptionalNumber = None, - top: OptionalNumber = None, - right: OptionalNumber = None, - bottom: OptionalNumber = None, - expand: Union[None, bool, int] = None, - expand_loose: Optional[bool] = None, - col: Optional[ResponsiveNumber] = None, - opacity: OptionalNumber = None, - rotate: Optional[RotateValue] = None, - scale: Optional[ScaleValue] = None, - offset: Optional[OffsetValue] = None, - aspect_ratio: OptionalNumber = None, - animate_opacity: Optional[AnimationValue] = None, - animate_size: Optional[AnimationValue] = None, - animate_position: Optional[AnimationValue] = None, - animate_rotation: Optional[AnimationValue] = None, - animate_scale: Optional[AnimationValue] = None, - animate_offset: Optional[AnimationValue] = None, - on_animation_end: OptionalControlEventCallable = None, - tooltip: Optional[TooltipValue] = None, - badge: Optional[BadgeValue] = None, - visible: Optional[bool] = None, - disabled: Optional[bool] = None, - data: Any = None, - ): - ConstrainedControl.__init__( - self, - ref=ref, - key=key, - width=width, - height=height, - left=left, - top=top, - right=right, - bottom=bottom, - expand=expand, - expand_loose=expand_loose, - col=col, - opacity=opacity, - rotate=rotate, - scale=scale, - offset=offset, - aspect_ratio=aspect_ratio, - animate_opacity=animate_opacity, - animate_size=animate_size, - animate_position=animate_position, - animate_rotation=animate_rotation, - animate_scale=animate_scale, - animate_offset=animate_offset, - on_animation_end=on_animation_end, - tooltip=tooltip, - badge=badge, - visible=visible, - disabled=disabled, - data=data, - ) - self.__on_scroll = EventHandler(lambda e: WebviewScrollEvent(e)) - self._add_event_handler("scroll", self.__on_scroll.get_handler()) - self.__on_console_message = EventHandler( - lambda e: WebviewConsoleMessageEvent(e) - ) - self._add_event_handler( - "console_message", self.__on_console_message.get_handler() - ) - self.__on_javascript_alert_dialog = EventHandler( - lambda e: WebviewJavaScriptEvent(e) - ) - self._add_event_handler( - "javascript_alert_dialog", self.__on_javascript_alert_dialog.get_handler() - ) - - self.url = url - self.enable_javascript = enable_javascript - self.prevent_link = prevent_link - self.bgcolor = bgcolor - self.on_page_started = on_page_started - self.on_page_ended = on_page_ended - self.on_web_resource_error = on_web_resource_error - self.on_progress = on_progress - self.on_url_change = on_url_change - self.on_scroll = on_scroll - self.on_console_message = on_console_message - self.on_javascript_alert_dialog = on_javascript_alert_dialog - - def _get_control_name(self): - return "webview" - - def _check_mobile_or_mac_platform(self): - assert self.page is not None, "WebView must be added to page first." - if self.page.platform not in [ - PagePlatform.ANDROID, - PagePlatform.IOS, - PagePlatform.MACOS, - ]: - raise FletUnsupportedPlatformException( - "This method is supported on Android, iOS and macOS platforms only." - ) - - def reload(self): - self._check_mobile_or_mac_platform() - self.invoke_method("reload") - - def can_go_back(self, wait_timeout: OptionalNumber = 10) -> bool: - self._check_mobile_or_mac_platform() - return ( - self.invoke_method( - "can_go_back", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - == "true" - ) - - def can_go_forward(self, wait_timeout: OptionalNumber = 10) -> bool: - self._check_mobile_or_mac_platform() - return ( - self.invoke_method( - "can_go_forward", - wait_for_result=True, - wait_timeout=wait_timeout, - ) - == "true" - ) - - def go_back(self): - self._check_mobile_or_mac_platform() - self.invoke_method("go_back") - - def go_forward(self): - self._check_mobile_or_mac_platform() - self.invoke_method("go_forward") - - def enable_zoom(self): - self._check_mobile_or_mac_platform() - self.invoke_method("enable_zoom") - - def disable_zoom(self): - self._check_mobile_or_mac_platform() - self.invoke_method("disable_zoom") - - def clear_cache(self): - self._check_mobile_or_mac_platform() - self.invoke_method("clear_cache") - - def clear_local_storage(self): - self._check_mobile_or_mac_platform() - self.invoke_method("clear_local_storage") - - def get_current_url(self, wait_timeout: OptionalNumber = 10) -> Optional[str]: - self._check_mobile_or_mac_platform() - return self.invoke_method( - "get_current_url", wait_for_result=True, wait_timeout=wait_timeout - ) - - def get_title(self, wait_timeout: OptionalNumber = 10) -> Optional[str]: - self._check_mobile_or_mac_platform() - return self.invoke_method( - "get_title", wait_for_result=True, wait_timeout=wait_timeout - ) - - def get_user_agent(self, wait_timeout: OptionalNumber = 10) -> Optional[str]: - self._check_mobile_or_mac_platform() - return self.invoke_method( - "get_user_agent", wait_for_result=True, wait_timeout=wait_timeout - ) - - def load_file(self, absolute_path: str): - self._check_mobile_or_mac_platform() - self.invoke_method("load_file", arguments={"path": absolute_path}) - - def load_request( - self, url: str, method: WebviewRequestMethod = WebviewRequestMethod.GET - ): - self._check_mobile_or_mac_platform() - self.invoke_method( - "load_request", - arguments={"url": url, "method": method.value}, - ) - - def run_javascript(self, value: str): - self._check_mobile_or_mac_platform() - self.invoke_method("run_javascript", arguments={"value": value}) - - def load_html(self, value: str, base_url: Optional[str] = None): - self._check_mobile_or_mac_platform() - self.invoke_method( - "load_html", arguments={"value": value, "base_url": base_url} - ) - - def scroll_to(self, x: int, y: int): - self._check_mobile_or_mac_platform() - self.invoke_method("scroll_to", arguments={"x": str(x), "y": str(y)}) - - def scroll_by(self, x: int, y: int): - self._check_mobile_or_mac_platform() - self.invoke_method("scroll_by", arguments={"x": str(x), "y": str(y)}) - - # bgcolor - @property - def bgcolor(self) -> Optional[ColorValue]: - return self.__bgcolor - - @bgcolor.setter - def bgcolor(self, value: Optional[ColorValue]): - self.__bgcolor = value - self._set_enum_attr("bgcolor", value, ColorEnums) - - # url - @property - def url(self) -> str: - return self._get_attr("url") - - @url.setter - def url(self, value: str): - self._set_attr("url", value) - if self.page: - self.load_request(value, WebviewRequestMethod.GET) - - # enable_javascript - @property - def enable_javascript(self) -> bool: - return self._get_attr("enableJavascript", data_type="bool", def_value=False) - - @enable_javascript.setter - def enable_javascript(self, value: Optional[bool]): - self._set_attr("enableJavascript", value) - if self.page and value is not None: - self.invoke_method( - "set_javascript_mode", - arguments={"value": str(value)}, - ) - - # prevent_link - @property - def prevent_link(self) -> str: - return self._get_attr("prevent_link") - - @prevent_link.setter - def prevent_link(self, value: str): - self._set_attr("prevent_link", value) - - # on_page_started - @property - def on_page_started(self) -> OptionalControlEventCallable: - return self._get_event_handler("page_started") - - @on_page_started.setter - def on_page_started(self, handler: OptionalControlEventCallable): - self._add_event_handler("page_started", handler) - - # on_page_ended - @property - def on_page_ended(self) -> OptionalControlEventCallable: - return self._get_event_handler("page_ended") - - @on_page_ended.setter - def on_page_ended(self, handler: OptionalControlEventCallable): - self._add_event_handler("page_ended", handler) - - # on_web_resource_error - @property - def on_web_resource_error(self) -> OptionalControlEventCallable: - return self._get_event_handler("web_resource_error") - - @on_web_resource_error.setter - def on_web_resource_error(self, handler: OptionalControlEventCallable): - self._add_event_handler("web_resource_error", handler) - - # on_progress - @property - def on_progress(self) -> OptionalControlEventCallable: - return self._get_event_handler("progress") - - @on_progress.setter - def on_progress(self, handler: OptionalControlEventCallable): - self._add_event_handler("progress", handler) - - # on_url_change - @property - def on_url_change(self) -> OptionalControlEventCallable: - return self._get_event_handler("url_change") - - @on_url_change.setter - def on_url_change(self, handler: OptionalControlEventCallable): - self._add_event_handler("url_change", handler) - - # on_scroll - @property - def on_scroll(self) -> OptionalEventCallable[WebviewScrollEvent]: - return self.__on_scroll.handler - - @on_scroll.setter - def on_scroll(self, handler: OptionalEventCallable[WebviewScrollEvent]): - self.__on_scroll.handler = handler - - # on_console_message - @property - def on_console_message(self) -> OptionalEventCallable[WebviewConsoleMessageEvent]: - return self.__on_console_message.handler - - @on_console_message.setter - def on_console_message( - self, handler: OptionalEventCallable[WebviewConsoleMessageEvent] - ): - self.__on_console_message.handler = handler - - # on_javascript_alert_dialog - @property - def on_javascript_alert_dialog( - self, - ) -> OptionalEventCallable[WebviewJavaScriptEvent]: - return self.__on_javascript_alert_dialog.handler - - @on_javascript_alert_dialog.setter - def on_javascript_alert_dialog( - self, handler: OptionalEventCallable[WebviewJavaScriptEvent] - ): - self.__on_javascript_alert_dialog.handler = handler diff --git a/sdk/python/packages/flet/src/flet/core/window_drag_area.py b/sdk/python/packages/flet/src/flet/core/window_drag_area.py deleted file mode 100644 index 23df8f10d..000000000 --- a/sdk/python/packages/flet/src/flet/core/window_drag_area.py +++ /dev/null @@ -1,74 +0,0 @@ -from typing import Any - -from flet.core.control import Control -from flet.core.gesture_detector import DragStartEvent, GestureDetector, TapEvent -from flet.core.types import OptionalEventCallable - - -class WindowDragArea(GestureDetector): - """ - A control for drag to move, maximize and restore application window. - - When you have hidden the title bar with `page.window_title_bar_hidden`, you can add this control to move the window position. - - Example: - ``` - import flet as ft - - def main(page: ft.Page): - page.window_title_bar_hidden = True - page.window_title_bar_buttons_hidden = True - - page.add( - ft.Row( - [ - ft.WindowDragArea(ft.Container(ft.Text("Drag this area to move, maximize and restore application window."), bgcolor=ft.colors.AMBER_300, padding=10), expand=True), - ft.IconButton(ft.icons.CLOSE, on_click=lambda _: page.window_close()) - ] - ) - ) - - ft.app(target=main) - ``` - - ----- - - Online docs: https://flet.dev/docs/controls/windowdragarea - """ - - def __init__( - self, - content: Control, - maximizable: bool = True, - on_double_tap: OptionalEventCallable["TapEvent"] = None, - on_pan_start: OptionalEventCallable["DragStartEvent"] = None, - **kwargs: Any, - ): - GestureDetector.__init__( - self, - content=content, - on_double_tap=self.handle_double_tap, - on_pan_start=self.handle_pan_start, - **kwargs, - ) - - self.maximizable = maximizable - self._on_double_tap = on_double_tap - self._on_pan_start = on_pan_start - - def before_update(self): - super().before_update() - assert self.content.visible, "content must be visible" - - def handle_double_tap(self, e: TapEvent): - if self.maximizable and self.page.window.maximizable: - self.page.window.maximized = not self.page.window.maximized - self.page.update() - - if self._on_double_tap is not None and self.page.window.maximized: - self._on_double_tap(e) - - def handle_pan_start(self, e: DragStartEvent): - self.page.window.start_dragging() - if self._on_pan_start is not None: - self._on_pan_start(e) diff --git a/sdk/python/packages/flet/src/flet/fastapi/__init__.py b/sdk/python/packages/flet/src/flet/fastapi/__init__.py index eb2d1fb05..5ed23e8e1 100644 --- a/sdk/python/packages/flet/src/flet/fastapi/__init__.py +++ b/sdk/python/packages/flet/src/flet/fastapi/__init__.py @@ -1 +1 @@ -from flet_web.fastapi import * +from flet_web.fastapi import * # noqa: F403 diff --git a/sdk/python/packages/flet/src/flet/flet_socket_server.py b/sdk/python/packages/flet/src/flet/flet_socket_server.py deleted file mode 100644 index fbd9f3f87..000000000 --- a/sdk/python/packages/flet/src/flet/flet_socket_server.py +++ /dev/null @@ -1,205 +0,0 @@ -import asyncio -import json -import logging -import os -import struct -import sys -import tempfile -from concurrent.futures import ThreadPoolExecutor -from pathlib import Path -from typing import List, Optional - -import flet -from flet.core.local_connection import LocalConnection -from flet.core.protocol import ( - ClientActions, - ClientMessage, - Command, - CommandEncoder, - PageCommandResponsePayload, - PageCommandsBatchResponsePayload, - RegisterWebClientRequestPayload, -) -from flet.core.pubsub.pubsub_hub import PubSubHub -from flet.utils import get_free_tcp_port, is_windows, random_string - -logger = logging.getLogger(flet.__name__) - - -class FletSocketServer(LocalConnection): - def __init__( - self, - loop: asyncio.AbstractEventLoop, - port: int = 0, - uds_path: Optional[str] = None, - on_event=None, - on_session_created=None, - blocking=False, - executor: Optional[ThreadPoolExecutor] = None, - ): - super().__init__() - self.__send_queue = asyncio.Queue() - self.__port = port - self.__uds_path = uds_path - self.__on_event = on_event - self.__on_session_created = on_session_created - self.__blocking = blocking - self.__loop = loop - self.__executor = executor - self.pubsubhub = PubSubHub(loop=loop, executor=executor) - self.__running_tasks = set() - - async def start(self): - self.__connected = False - self.__receive_loop_task = None - self.__send_loop_task = None - if is_windows() or self.__port > 0: - # TCP - host = "localhost" - port = self.__port if self.__port > 0 else get_free_tcp_port() - self.page_url = f"tcp://{host}:{port}" - logger.info(f"Starting up TCP server on {host}:{port}") - server = await asyncio.start_server(self.handle_connection, host, port) - else: - # UDS - if not self.__uds_path: - self.__uds_path = str( - Path(tempfile.gettempdir()).joinpath(random_string(10)) - ) - if os.path.exists(self.__uds_path): - os.remove(self.__uds_path) - self.page_url = self.__uds_path - logger.info(f"Starting up UDS server on {self.__uds_path}") - server = await asyncio.start_unix_server( - self.handle_connection, self.__uds_path - ) - - if self.__blocking: - self.__server = None - await server.serve_forever() - else: - self.__server = asyncio.create_task(server.serve_forever()) - - async def handle_connection( - self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter - ): - if not self.__connected: - self.__connected = True - logger.debug("Connected new TCP client") - self.__receive_loop_task = asyncio.create_task(self.__receive_loop(reader)) - self.__send_loop_task = asyncio.create_task(self.__send_loop(writer)) - - async def __receive_loop(self, reader: asyncio.StreamReader): - while True: - try: - raw_msglen = await reader.readexactly(4) - except Exception: - return None - - if not raw_msglen: - return None - msglen = struct.unpack(">I", raw_msglen)[0] - - data = await reader.readexactly(msglen) - await self.__on_message(data.decode("utf-8")) - - async def __send_loop(self, writer: asyncio.StreamWriter): - while True: - message = await self.__send_queue.get() - try: - data = message.encode("utf-8") - msg = struct.pack(">I", len(data)) + data - writer.write(msg) - # await writer.drain() - logger.debug(f"sent to TCP: {len(msg)}") - except Exception: - # re-enqueue the message to repeat it when re-connected - self.__send_queue.put_nowait(message) - raise - - async def __on_message(self, data: str): - logger.debug(f"_on_message: {data}") - msg_dict = json.loads(data) - msg = ClientMessage(**msg_dict) - task = None - if msg.action == ClientActions.REGISTER_WEB_CLIENT: - self._client_details = RegisterWebClientRequestPayload(**msg.payload) - - # register response - self.__send(self._create_register_web_client_response()) - - # start session - if self.__on_session_created is not None: - task = asyncio.create_task( - self.__on_session_created(self._create_session_handler_arg()) - ) - - elif msg.action == ClientActions.PAGE_EVENT_FROM_WEB: - if self.__on_event is not None: - task = asyncio.create_task( - self.__on_event(self._create_page_event_handler_arg(msg)) - ) - - elif msg.action == ClientActions.UPDATE_CONTROL_PROPS: - if self.__on_event is not None: - task = asyncio.create_task( - self.__on_event(self._create_update_control_props_handler_arg(msg)) - ) - else: - # it's something else - raise Exception(f'Unknown message "{msg.action}": {msg.payload}') - - if task: - self.__running_tasks.add(task) - task.add_done_callback(self.__running_tasks.discard) - - def send_command(self, session_id: str, command: Command): - result, message = self._process_command(command) - if message: - self.__send(message) - return PageCommandResponsePayload(result=result, error="") - - def send_commands(self, session_id: str, commands: List[Command]): - results = [] - messages = [] - for command in commands: - result, message = self._process_command(command) - if command.name in ["add", "get"]: - results.append(result) - if message: - messages.append(message) - if len(messages) > 0: - self.__send(ClientMessage(ClientActions.PAGE_CONTROLS_BATCH, messages)) - return PageCommandsBatchResponsePayload(results=results, error="") - - def __send(self, message: ClientMessage): - j = json.dumps(message, cls=CommandEncoder, separators=(",", ":")) - logger.debug(f"__send: {j}") - self.__loop.call_soon_threadsafe(self.__send_queue.put_nowait, j) - - async def close(self): - logger.debug("Closing connection...") - - logger.debug(f"Disconnecting all pages...") - while self.sessions: - _, page = self.sessions.popitem() - await page._disconnect(0) - - if self.__executor: - logger.debug("Shutting down thread pool...") - if sys.version_info >= (3, 9): - self.__executor.shutdown(wait=False, cancel_futures=True) - else: - self.__executor.shutdown(wait=False) - - # close socket - if self.__receive_loop_task: - self.__receive_loop_task.cancel() - if self.__send_loop_task: - self.__send_loop_task.cancel() - if self.__server: - self.__server.cancel() - - # remove UDS path - if self.__uds_path and os.path.exists(self.__uds_path): - os.unlink(self.__uds_path) diff --git a/sdk/python/packages/flet/src/flet/map/__init__.py b/sdk/python/packages/flet/src/flet/map/__init__.py deleted file mode 100644 index c77fe347c..000000000 --- a/sdk/python/packages/flet/src/flet/map/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from flet.core.map.circle_layer import CircleLayer, CircleMarker -from flet.core.map.map import ( - Map, - MapEvent, - MapEventSource, - MapHoverEvent, - MapInteractionConfiguration, - MapInteractiveFlag, - MapLatitudeLongitude, - MapLatitudeLongitudeBounds, - MapMultiFingerGesture, - MapPointerDeviceType, - MapPointerEvent, - MapPositionChangeEvent, - MapTapEvent, -) -from flet.core.map.marker_layer import Marker, MarkerLayer -from flet.core.map.polygon_layer import PolygonLayer, PolygonMarker -from flet.core.map.polyline_layer import ( - DashedStrokePattern, - DottedStrokePattern, - PatternFit, - PolylineLayer, - PolylineMarker, - SolidStrokePattern, -) -from flet.core.map.rich_attribution import RichAttribution -from flet.core.map.simple_attribution import SimpleAttribution -from flet.core.map.text_source_attribution import TextSourceAttribution -from flet.core.map.tile_layer import MapTileLayerEvictErrorTileStrategy, TileLayer diff --git a/sdk/python/packages/flet/src/flet/matplotlib_chart.py b/sdk/python/packages/flet/src/flet/matplotlib_chart.py deleted file mode 100644 index 3907aa926..000000000 --- a/sdk/python/packages/flet/src/flet/matplotlib_chart.py +++ /dev/null @@ -1 +0,0 @@ -from flet.core.matplotlib_chart import MatplotlibChart diff --git a/sdk/python/packages/flet/src/flet/messaging/connection.py b/sdk/python/packages/flet/src/flet/messaging/connection.py new file mode 100644 index 000000000..377651a4c --- /dev/null +++ b/sdk/python/packages/flet/src/flet/messaging/connection.py @@ -0,0 +1,60 @@ +import logging +from asyncio import AbstractEventLoop +from concurrent.futures import ThreadPoolExecutor +from typing import Any, Optional + +from flet.messaging.protocol import ClientMessage +from flet.pubsub.pubsub_hub import PubSubHub + +logger = logging.getLogger("flet") + + +class Connection: + def __init__(self): + self.page_name: str = "" + self.page_url: Optional[str] = None + self.__pubsubhub = None + self.__loop: Optional[AbstractEventLoop] = None + self.__executor: Optional[ThreadPoolExecutor] = None + + @property + def loop(self) -> AbstractEventLoop: + if self.__loop is None: + raise Exception("Loop not initialized") + return self.__loop + + @loop.setter + def loop(self, value): + self.__loop = value + + @property + def executor(self) -> ThreadPoolExecutor: + if self.__executor is None: + raise Exception("Executor not initialized") + return self.__executor + + @executor.setter + def executor(self, value): + self.__executor = value + + @property + def pubsubhub(self) -> PubSubHub: + if self.__pubsubhub is None: + raise Exception("PubSubHub not initialized") + return self.__pubsubhub + + @pubsubhub.setter + def pubsubhub(self, value: PubSubHub): + self.__pubsubhub = value + + def send_message(self, message: ClientMessage): + raise NotImplementedError() + + def get_upload_url(self, file_name: str, expires: int) -> str: + raise NotImplementedError() + + def oauth_authorize(self, attrs: dict[str, Any]): + raise NotImplementedError() + + def dispose(self): + pass diff --git a/sdk/python/packages/flet/src/flet/messaging/flet_socket_server.py b/sdk/python/packages/flet/src/flet/messaging/flet_socket_server.py new file mode 100644 index 000000000..14a493420 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/messaging/flet_socket_server.py @@ -0,0 +1,211 @@ +import asyncio +import logging +import os +import tempfile +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path +from typing import Any, Optional + +import msgpack +from flet.controls.base_control import BaseControl +from flet.messaging.connection import Connection +from flet.messaging.protocol import ( + ClientAction, + ClientMessage, + ControlEventBody, + InvokeMethodResponseBody, + RegisterClientRequestBody, + RegisterClientResponseBody, + UpdateControlPropsBody, + configure_encode_object_for_msgpack, + decode_ext_from_msgpack, +) +from flet.messaging.session import Session +from flet.pubsub.pubsub_hub import PubSubHub +from flet.utils import get_free_tcp_port, is_windows, random_string + +logger = logging.getLogger("flet") +transport_log = logging.getLogger("flet_transport") + + +class FletSocketServer(Connection): + def __init__( + self, + loop: asyncio.AbstractEventLoop, + port: int = 0, + uds_path: Optional[str] = None, + on_session_created=None, + before_main=None, + blocking=False, + executor: Optional[ThreadPoolExecutor] = None, + ): + super().__init__() + self.__send_loop_task = None + self.__receive_loop_task = None + self.__connected = None + self.session = None + self.__send_queue = asyncio.Queue() + self.__port = port + self.__uds_path = uds_path + self.__on_session_created = on_session_created + self.__before_main = before_main + self.__blocking = blocking + self.__running_tasks = set() + self.loop = loop + self.executor = executor + self.pubsubhub = PubSubHub(loop=loop, executor=executor) + + async def start(self): + self.__connected = False + self.__receive_loop_task = None + self.__send_loop_task = None + if is_windows() or self.__port > 0: + # TCP + host = "localhost" + port = self.__port if self.__port > 0 else get_free_tcp_port() + self.page_url = f"tcp://{host}:{port}" + logger.info(f"Starting up TCP server on {host}:{port}") + server = await asyncio.start_server(self.handle_connection, host, port) + else: + # UDS + if not self.__uds_path: + self.__uds_path = str( + Path(tempfile.gettempdir()).joinpath(random_string(10)) + ) + if os.path.exists(self.__uds_path): + os.remove(self.__uds_path) + self.page_url = self.__uds_path + logger.info(f"Starting up UDS server on {self.__uds_path}") + server = await asyncio.start_unix_server( + self.handle_connection, self.__uds_path + ) + + if self.__blocking: + self.__server = None + await server.serve_forever() + else: + self.__server = asyncio.create_task(server.serve_forever()) + + async def handle_connection( + self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter + ): + if not self.__connected: + self.__connected = True + logger.debug("Connected new TCP client") + self.__receive_loop_task = asyncio.create_task(self.__receive_loop(reader)) + self.__send_loop_task = asyncio.create_task(self.__send_loop(writer)) + + async def __receive_loop(self, reader: asyncio.StreamReader): + unpacker = msgpack.Unpacker(ext_hook=decode_ext_from_msgpack) + while True: + try: + buf = await reader.read(1024**2) + if not buf: + return None + unpacker.feed(buf) + except Exception as e: + logger.debug(f"Error receiving socket data from Flet client: {e}") + return None + + for msg in unpacker: + await self.__on_message(msg) + + async def __send_loop(self, writer: asyncio.StreamWriter): + while True: + message = await self.__send_queue.get() + try: + writer.write(message) + except Exception: + # re-enqueue the message to repeat it when re-connected + self.__send_queue.put_nowait(message) + raise + + async def __on_message(self, data: Any): + action = ClientAction(data[0]) + body = data[1] + transport_log.debug(f"_on_message: {action} {body}") + task = None + if action == ClientAction.REGISTER_CLIENT: + req = RegisterClientRequestBody(**body) + + # create new session + self.session = Session(self) + + try: + # apply page patch + if not req.session_id: + self.session.apply_page_patch(req.page) + + if asyncio.iscoroutinefunction(self.__before_main): + await self.__before_main(self.session.page) + elif callable(self.__before_main): + self.__before_main(self.session.page) + + # register response + self.send_message( + ClientMessage( + ClientAction.REGISTER_CLIENT, + RegisterClientResponseBody( + session_id=self.session.id, + page_patch=self.session.get_page_patch(), + error="", + ), + ) + ) + + # start session + if self.__on_session_created is not None: + task = asyncio.create_task(self.__on_session_created(self.session)) + except Exception as ex: + logger.debug(f"Error creating session: {ex}", exc_info=True) + + elif action == ClientAction.CONTROL_EVENT: + req = ControlEventBody(**body) + task = asyncio.create_task( + self.session.dispatch_event(req.target, req.name, req.data) + ) + + elif action == ClientAction.UPDATE_CONTROL_PROPS: + req = UpdateControlPropsBody(**body) + self.session.apply_patch(req.id, req.props) + + elif action == ClientAction.INVOKE_METHOD: + req = InvokeMethodResponseBody(**body) + self.session.handle_invoke_method_results( + req.control_id, req.call_id, req.result, req.error + ) + + else: + # it's something else + raise Exception(f'Unknown message "{action}": {body}') + + if task: + self.__running_tasks.add(task) + task.add_done_callback(self.__running_tasks.discard) + + def send_message(self, message: ClientMessage): + transport_log.debug(f"send_message: {message}") + m = msgpack.packb( + [message.action, message.body], + default=configure_encode_object_for_msgpack(BaseControl), + ) + self.__send_queue.put_nowait(m) + + async def close(self): + logger.debug("Closing connection...") + + if self.executor: + logger.debug("Shutting down thread pool...") + self.executor.shutdown(wait=False, cancel_futures=True) + + # close socket + if self.__receive_loop_task: + self.__receive_loop_task.cancel() + if self.__send_loop_task: + self.__send_loop_task.cancel() + if self.__server: + self.__server.cancel() + + # remove UDS path + if self.__uds_path and os.path.exists(self.__uds_path): + os.unlink(self.__uds_path) diff --git a/sdk/python/packages/flet/src/flet/messaging/protocol.py b/sdk/python/packages/flet/src/flet/messaging/protocol.py new file mode 100644 index 000000000..caca229b1 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/messaging/protocol.py @@ -0,0 +1,149 @@ +import datetime +from dataclasses import dataclass, fields, is_dataclass +from enum import Enum +from typing import Any + +import msgpack +from flet.controls.duration import Duration + + +def configure_encode_object_for_msgpack(control_cls): + def encode_object_for_msgpack(obj): + if is_dataclass(obj): + r = {} + prev_lists = {} + prev_dicts = {} + prev_classes = {} + for field in fields(obj): + if "skip" in field.metadata: # or hasattr(obj, f"_prev_{field.name}"): + continue + v = getattr(obj, field.name) + if isinstance(v, list): + v = v[:] + if len(v) > 0: + r[field.name] = v + prev_lists[field.name] = v + elif isinstance(v, dict): + v = v.copy() + if len(v) > 0: + r[field.name] = v + prev_dicts[field.name] = v + elif field.name.startswith("on_"): + v = v is not None + if v: + r[field.name] = v + elif is_dataclass(v): + r[field.name] = v + prev_classes[field.name] = v + elif v is not None and ( + v != field.default or not isinstance(obj, control_cls) + ): + r[field.name] = v + + if not hasattr(obj, "_frozen"): + setattr(obj, "__prev_lists", prev_lists) + setattr(obj, "__prev_dicts", prev_dicts) + setattr(obj, "__prev_classes", prev_classes) + # print("__prev_cols", obj.__class__.__name__, prev_cols.keys()) + + return r + elif isinstance(obj, Enum): + return obj.value + elif isinstance(obj, (datetime.datetime, datetime.date)): + if isinstance(obj, datetime.datetime) and obj.tzinfo is None: + obj = obj.astimezone() + return msgpack.ExtType(1, obj.isoformat().encode("utf-8")) + elif isinstance(obj, datetime.time): + return msgpack.ExtType(2, obj.strftime("%H:%M").encode("utf-8")) + elif isinstance(obj, Duration): + return msgpack.ExtType(3, obj.in_microseconds) + elif callable(obj): + raise Exception(f"Cannot serialize method: {obj}") from None + return obj + + return encode_object_for_msgpack + + +def decode_ext_from_msgpack(code, data): + if code == 1: + return datetime.datetime.fromisoformat(data.decode("utf-8")) + elif code == 2: + return datetime.time(*map(int, data.decode("utf-8").split(":"))) + elif code == 3: + return Duration.from_unit(microseconds=int(data)) + return msgpack.ExtType(code, data) + + +class ClientAction(Enum): + REGISTER_CLIENT = 1 + PATCH_CONTROL = 2 + CONTROL_EVENT = 3 + UPDATE_CONTROL_PROPS = 4 + INVOKE_METHOD = 5 + SESSION_CRASHED = 6 + + +@dataclass +class ClientMessage: + action: ClientAction + body: Any + + +@dataclass +class RegisterClientRequestBody: + session_id: str + page_name: str + page: dict[str, Any] + + +@dataclass +class SessionPayload: + id: str + controls: dict[str, dict[str, Any]] + + +@dataclass +class RegisterClientResponseBody: + session_id: str + page_patch: Any + error: str + + +@dataclass +class PatchControlBody: + id: int + patch: Any + + +@dataclass +class UpdateControlPropsBody: + id: int + props: Any + + +@dataclass +class ControlEventBody: + target: int + name: str + data: Any + + +@dataclass +class SessionCrashedBody: + message: str + + +@dataclass +class InvokeMethodRequestBody: + control_id: int + call_id: str + name: str + args: dict[str, Any] + + +@dataclass +class InvokeMethodResponseBody: + control_id: int + call_id: str + result: Any + error: str diff --git a/sdk/python/packages/flet/src/flet/messaging/pyodide_connection.py b/sdk/python/packages/flet/src/flet/messaging/pyodide_connection.py new file mode 100644 index 000000000..479ba9f0e --- /dev/null +++ b/sdk/python/packages/flet/src/flet/messaging/pyodide_connection.py @@ -0,0 +1,122 @@ +import asyncio +import logging +from typing import Any + +import flet_js +import msgpack +from flet.controls.base_control import BaseControl +from flet.messaging.connection import Connection +from flet.messaging.protocol import ( + ClientAction, + ClientMessage, + ControlEventBody, + InvokeMethodResponseBody, + RegisterClientRequestBody, + RegisterClientResponseBody, + UpdateControlPropsBody, + configure_encode_object_for_msgpack, + decode_ext_from_msgpack, +) +from flet.messaging.session import Session +from flet.pubsub.pubsub_hub import PubSubHub + +logger = logging.getLogger("flet") +transport_log = logging.getLogger("flet_transport") + + +class PyodideConnection(Connection): + def __init__(self, on_session_created, before_main): + super().__init__() + self.__receive_queue = asyncio.Queue() + self.__on_session_created = on_session_created + self.__before_main = before_main + flet_js.start_connection = self.connect + self.__running_tasks = set() + self.pubsubhub = PubSubHub() + self.loop = asyncio.get_running_loop() + + async def connect(self, send_callback): + logger.info("Starting Pyodide connection...") + self.page_url = flet_js.documentUrl + self.send_callback = send_callback + asyncio.create_task(self.receive_loop()) + flet_js.send = self.send_from_js + + async def receive_loop(self): + while True: + data = await self.__receive_queue.get() + message = msgpack.unpackb(data.to_py(), ext_hook=decode_ext_from_msgpack) + await self.__on_message(message) + + def send_from_js(self, message: Any): + self.__receive_queue.put_nowait(message) + + async def __on_message(self, data: Any): + action = ClientAction(data[0]) + body = data[1] + transport_log.debug(f"_on_message: {action} {body}") + task = None + if action == ClientAction.REGISTER_CLIENT: + req = RegisterClientRequestBody(**body) + + try: + # create new session + self.session = Session(self) + + # apply page patch + self.session.apply_page_patch(req.page) + + if asyncio.iscoroutinefunction(self.__before_main): + await self.__before_main(self.session.page) + elif callable(self.__before_main): + self.__before_main(self.session.page) + + # register response + self.send_message( + ClientMessage( + ClientAction.REGISTER_CLIENT, + RegisterClientResponseBody( + session_id=self.session.id, + page_patch=self.session.get_page_patch(), + error="", + ), + ) + ) + + # start session + if self.__on_session_created is not None: + task = asyncio.create_task(self.__on_session_created(self.session)) + except Exception as ex: + logger.debug(f"Error creating session: {ex}", exc_info=True) + + elif action == ClientAction.CONTROL_EVENT: + req = ControlEventBody(**body) + task = asyncio.create_task( + self.session.dispatch_event(req.target, req.name, req.data) + ) + + elif action == ClientAction.UPDATE_CONTROL_PROPS: + req = UpdateControlPropsBody(**body) + self.session.apply_patch(req.id, req.props) + + elif action == ClientAction.INVOKE_METHOD: + req = InvokeMethodResponseBody(**body) + self.session.handle_invoke_method_results( + req.control_id, req.call_id, req.result, req.error + ) + + else: + # it's something else + raise Exception(f'Unknown message "{action}": {body}') + + if task: + self.__running_tasks.add(task) + task.add_done_callback(self.__running_tasks.discard) + + def send_message(self, message: ClientMessage): + transport_log.debug(f"send_message: {message}") + m = msgpack.packb( + [message.action, message.body], + default=configure_encode_object_for_msgpack(BaseControl), + ) + self.send_callback(m) diff --git a/sdk/python/packages/flet/src/flet/messaging/session.py b/sdk/python/packages/flet/src/flet/messaging/session.py new file mode 100644 index 000000000..eee5f8605 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/messaging/session.py @@ -0,0 +1,282 @@ +import asyncio +import inspect +import logging +import traceback +import weakref +from datetime import datetime, timedelta, timezone +from typing import Any, Optional + +from flet.controls.base_control import BaseControl +from flet.controls.control_event import ControlEvent +from flet.controls.object_patch import ObjectPatch +from flet.controls.page import Page, _session_page +from flet.controls.update_behavior import UpdateBehavior +from flet.messaging.connection import Connection +from flet.messaging.protocol import ( + ClientAction, + ClientMessage, + InvokeMethodRequestBody, + PatchControlBody, + SessionCrashedBody, +) +from flet.pubsub.pubsub_client import PubSubClient +from flet.utils.from_dict import from_dict +from flet.utils.object_model import get_param_count, patch_dataclass +from flet.utils.strings import random_string + +logger = logging.getLogger("flet") + +__all__ = ["Session"] + + +class Session: + def __init__(self, conn: Connection): + self.__conn = conn + self.__id = random_string(16) + self.__expires_at = None + self.__index: weakref.WeakValueDictionary[int, BaseControl] = ( + weakref.WeakValueDictionary() + ) + self.__page = Page(self) + self.__index[self.__page._i] = self.__page + self.__pubsub_client = PubSubClient(conn.pubsubhub, self.__id) + + session_id = self.__id + weakref.finalize( + self, lambda: logger.debug(f"Session was garbage collected: {session_id}") + ) + + @property + def connection(self) -> Connection: + return self.__conn + + @property + def id(self): + return self.__id + + @property + def expires_at(self) -> Optional[datetime]: + return self.__expires_at + + @property + def index(self): + return self.__index + + @property + def page(self): + return self.__page + + @property + def pubsub_client(self) -> PubSubClient: + return self.__pubsub_client + + async def connect(self, conn: Connection) -> None: + logger.debug(f"Connect session: {self.id}") + _session_page.set(self.__page) + self.__conn = conn + self.__expires_at = None + await self.dispatch_event(self.__page._i, "connect", None) + + async def disconnect(self, session_timeout_seconds: int) -> None: + logger.debug(f"Disconnect session: {self.id}") + self.__expires_at = datetime.now(timezone.utc) + timedelta( + seconds=session_timeout_seconds + ) + if self.__conn: + self.__conn.dispose() + self.__conn = None + await self.dispatch_event(self.__page._i, "disconnect", None) + + def close(self): + logger.debug(f"Closing expired session: {self.id}") + self.__pubsub_client.unsubscribe_all() + asyncio.create_task(self.dispatch_event(self.__page._i, "close", None)) + + def patch_control(self, control: BaseControl): + patch, added_controls, removed_controls = self.__get_update_control_patch( + control=control, prev_control=control + ) + + # print(f"\n\nremoved_controls: ({len(removed_controls)})") + # for c in removed_controls: + # print(f"\n\nremoved_control: {c._c}({c._i} - {id(c)})") + + for removed_control in removed_controls: + if not any(added._i == removed_control._i for added in added_controls): + removed_control.will_unmount() + self.__index.pop(removed_control._i, None) + + if len(patch) > 1: + self.connection.send_message( + ClientMessage( + ClientAction.PATCH_CONTROL, PatchControlBody(control._i, patch) + ) + ) + + # print(f"\n\nadded_controls: ({len(added_controls)})") + # for ac in added_controls: + # print(f"\n\nadded_control: {ac._c}({ac._i} - {id(ac)})") + + for added_control in added_controls: + self.__index[added_control._i] = added_control + if not any(removed._i == added_control._i for removed in removed_controls): + added_control.did_mount() + + def apply_patch(self, control_id: int, patch: dict[str, Any]): + if control := self.__index.get(control_id): + patch_dataclass(control, patch) + + def apply_page_patch(self, patch: dict[str, Any]): + self.apply_patch(self.__page._i, patch) + + def get_page_patch(self): + patch, added_controls, _ = self.__get_update_control_patch( + self.__page, prev_control=None + ) + + for added_control in added_controls: + self.__index[added_control._i] = added_control + added_control.did_mount() + + # patch format: + # [[], , , ...] + # := [, , , ] + return patch[1][3] # [1] - 1st operation -> [3] - Page + + # optimizations: + # - disable auto-update + # - auto-update to skip already updated items + # - add-only list + # - disable mount/unmount + + async def dispatch_event( + self, + control_id: int, + event_name: str, + event_data: Any, + ): + control = self.__index.get(control_id) + if not control: + logger.debug(f"Control with ID {control_id} not found.") + return + + field_name = f"on_{event_name}" + if not hasattr(control, field_name): + # field_name not defined + return + try: + event_type = ControlEvent.get_event_field_type(control, field_name) + if event_type is None: + return + + if event_type == ControlEvent or not isinstance(event_data, dict): + # simple ControlEvent + e = ControlEvent(control=control, name=event_name, data=event_data) + else: + # custom ControlEvent + args = { + "control": control, + "name": event_name, + **(event_data or {}), + } + e = from_dict(event_type, args) + + handle_event = control.before_event(e) + + if handle_event is None or handle_event: + _session_page.set(self.__page) + UpdateBehavior.reset() + + # Handle async and sync event handlers accordingly + event_handler = getattr(control, field_name) + if asyncio.iscoroutinefunction(event_handler): + if get_param_count(event_handler) == 0: + await event_handler() + else: + await event_handler(e) + + elif inspect.isasyncgenfunction(event_handler): + if get_param_count(event_handler) == 0: + async for _ in event_handler(): + if UpdateBehavior.auto_update_enabled(): + await self.auto_update(self.index.get(control._i)) + else: + async for _ in event_handler(e): + if UpdateBehavior.auto_update_enabled(): + await self.auto_update(self.index.get(control._i)) + + elif inspect.isgeneratorfunction(event_handler): + if get_param_count(event_handler) == 0: + for _ in event_handler(): + if UpdateBehavior.auto_update_enabled(): + await self.auto_update(self.index.get(control._i)) + else: + for _ in event_handler(e): + if UpdateBehavior.auto_update_enabled(): + await self.auto_update(self.index.get(control._i)) + + elif callable(event_handler): + if get_param_count(event_handler) == 0: + event_handler() + else: + event_handler(e) + + if UpdateBehavior.auto_update_enabled(): + await self.auto_update(self.index.get(control._i)) + + except Exception as ex: + tb = traceback.format_exc() + self.error(f"Exception in '{field_name}': {ex}\n{tb}") + + def invoke_method(self, control_id: int, call_id: str, method_name: str, args: Any): + self.connection.send_message( + ClientMessage( + ClientAction.INVOKE_METHOD, + InvokeMethodRequestBody( + control_id=control_id, call_id=call_id, name=method_name, args=args + ), + ) + ) + + def handle_invoke_method_results( + self, control_id: int, call_id: str, result: Any, error: Optional[str] + ): + if control := self.__index.get(control_id): + control._handle_invoke_method_results( + call_id=call_id, result=result, error=error + ) + else: + raise Exception( + f"Error handling invoke method results. Control with ID {control_id} " + "is not registered." + ) + + async def auto_update(self, control: BaseControl | None): + while control: + if ( + control.is_isolated() + and not hasattr(control, "_frozen") + and self.connection + ): + control.update() + break + control = control.parent + + def error(self, message: str): + self.connection.send_message( + ClientMessage(ClientAction.SESSION_CRASHED, SessionCrashedBody(message)) + ) + + def __get_update_control_patch( + self, control: BaseControl, prev_control: Optional[BaseControl] + ): + # calculate patch + patch, added_controls, removed_controls = ObjectPatch.from_diff( + prev_control, + control, + control_cls=BaseControl, + ) + + # print("\n\npatch:", patch) + + return patch.to_message(), added_controls, removed_controls diff --git a/sdk/python/packages/flet/src/flet/plotly_chart.py b/sdk/python/packages/flet/src/flet/plotly_chart.py deleted file mode 100644 index 6b6670cbf..000000000 --- a/sdk/python/packages/flet/src/flet/plotly_chart.py +++ /dev/null @@ -1 +0,0 @@ -from flet.core.plotly_chart import PlotlyChart diff --git a/sdk/python/packages/flet/src/flet/core/pubsub/pubsub_client.py b/sdk/python/packages/flet/src/flet/pubsub/pubsub_client.py similarity index 91% rename from sdk/python/packages/flet/src/flet/core/pubsub/pubsub_client.py rename to sdk/python/packages/flet/src/flet/pubsub/pubsub_client.py index 46932a37a..392c40fb1 100644 --- a/sdk/python/packages/flet/src/flet/core/pubsub/pubsub_client.py +++ b/sdk/python/packages/flet/src/flet/pubsub/pubsub_client.py @@ -1,10 +1,9 @@ import logging from typing import Any, Callable -import flet.core -from flet.core.pubsub.pubsub_hub import PubSubHub +from flet.pubsub.pubsub_hub import PubSubHub -logger = logging.getLogger(flet.__name__) +logger = logging.getLogger("flet") class PubSubClient: diff --git a/sdk/python/packages/flet/src/flet/core/pubsub/pubsub_hub.py b/sdk/python/packages/flet/src/flet/pubsub/pubsub_hub.py similarity index 93% rename from sdk/python/packages/flet/src/flet/core/pubsub/pubsub_hub.py rename to sdk/python/packages/flet/src/flet/pubsub/pubsub_hub.py index 1a69e6498..e1eb448ca 100644 --- a/sdk/python/packages/flet/src/flet/core/pubsub/pubsub_hub.py +++ b/sdk/python/packages/flet/src/flet/pubsub/pubsub_hub.py @@ -1,14 +1,14 @@ import asyncio import logging import threading +from collections.abc import Awaitable, Iterable from concurrent.futures import ThreadPoolExecutor -from typing import Any, Awaitable, Callable, Dict, Iterable, Optional, Union +from typing import Any, Callable, Optional, Union -import flet.core -from flet.core.locks import NopeLock from flet.utils import is_pyodide +from flet.utils.locks import NopeLock -logger = logging.getLogger(flet.__name__) +logger = logging.getLogger("flet") class PubSubHub: @@ -21,14 +21,14 @@ def __init__( self.__loop = loop self.__executor = executor self.__lock = threading.Lock() if not is_pyodide() else NopeLock() - self.__subscribers: Dict[ + self.__subscribers: dict[ str, set[Union[Callable, Callable[..., Awaitable[Any]]]] ] = {} # key: session_id, value: handler - self.__topic_subscribers: Dict[ - str, Dict[str, set[Union[Callable, Callable[..., Awaitable[Any]]]]] + self.__topic_subscribers: dict[ + str, dict[str, set[Union[Callable, Callable[..., Awaitable[Any]]]]] ] = {} # key: topic, value: dict[session_id, handler] - self.__subscriber_topics: Dict[ - str, Dict[str, set[Union[Callable, Callable[..., Awaitable[Any]]]]] + self.__subscriber_topics: dict[ + str, dict[str, set[Union[Callable, Callable[..., Awaitable[Any]]]]] ] = {} # key: session_id, value: dict[topic, handler] def send_all(self, message: Any): diff --git a/sdk/python/packages/flet/src/flet/pyodide_connection.py b/sdk/python/packages/flet/src/flet/pyodide_connection.py deleted file mode 100644 index c058d8a0d..000000000 --- a/sdk/python/packages/flet/src/flet/pyodide_connection.py +++ /dev/null @@ -1,104 +0,0 @@ -import asyncio -import json -import logging -from typing import List - -import flet_js -from flet.core.local_connection import LocalConnection -from flet.core.protocol import ( - ClientActions, - ClientMessage, - Command, - CommandEncoder, - PageCommandResponsePayload, - PageCommandsBatchResponsePayload, - RegisterWebClientRequestPayload, -) - -import flet - -logger = logging.getLogger(flet.__name__) - - -class PyodideConnection(LocalConnection): - def __init__( - self, - on_event, - on_session_created, - ): - super().__init__() - self.__receive_queue = asyncio.Queue() - self.__on_event = on_event - self.__on_session_created = on_session_created - flet_js.start_connection = self.connect - - async def connect(self, send_callback): - logger.info("Starting Pyodide connection...") - self.page_url = flet_js.documentUrl - self.send_callback = send_callback - asyncio.create_task(self.receive_loop()) - flet_js.send = self.send_from_js - - async def receive_loop(self): - while True: - message = await self.__receive_queue.get() - await self.__on_message(message) - - def send_from_js(self, message: str): - logger.debug(f"Sending data from JavaScript to Python: {message}") - self.__receive_queue.put_nowait(message) - - async def __on_message(self, data: str): - logger.debug(f"_on_message: {data}") - msg_dict = json.loads(data) - msg = ClientMessage(**msg_dict) - if msg.action == ClientActions.REGISTER_WEB_CLIENT: - self._client_details = RegisterWebClientRequestPayload(**msg.payload) - - # register response - self.__send(self._create_register_web_client_response()) - - # start session - if self.__on_session_created is not None: - asyncio.create_task( - self.__on_session_created(self._create_session_handler_arg()) - ) - - elif msg.action == ClientActions.PAGE_EVENT_FROM_WEB: - if self.__on_event is not None: - asyncio.create_task( - self.__on_event(self._create_page_event_handler_arg(msg)) - ) - - elif msg.action == ClientActions.UPDATE_CONTROL_PROPS: - if self.__on_event is not None: - asyncio.create_task( - self.__on_event(self._create_update_control_props_handler_arg(msg)) - ) - else: - # it's something else - raise Exception(f'Unknown message "{msg.action}": {msg.payload}') - - def send_command(self, session_id: str, command: Command): - result, message = self._process_command(command) - if message: - self.__send(message) - return PageCommandResponsePayload(result=result, error="") - - def send_commands(self, session_id: str, commands: List[Command]): - results = [] - messages = [] - for command in commands: - result, message = self._process_command(command) - if command.name in ["add", "get"]: - results.append(result) - if message: - messages.append(message) - if len(messages) > 0: - self.__send(ClientMessage(ClientActions.PAGE_CONTROLS_BATCH, messages)) - return PageCommandsBatchResponsePayload(results=results, error="") - - def __send(self, message: ClientMessage): - j = json.dumps(message, cls=CommandEncoder, separators=(",", ":")) - logger.debug(f"__send: {j}") - self.send_callback(j) diff --git a/sdk/python/packages/flet/src/flet/security/__init__.py b/sdk/python/packages/flet/src/flet/security/__init__.py index 4b8811b3a..7c0b889ec 100644 --- a/sdk/python/packages/flet/src/flet/security/__init__.py +++ b/sdk/python/packages/flet/src/flet/security/__init__.py @@ -7,8 +7,10 @@ from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.ciphers.aead import AESGCM from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -except ImportError: - raise Exception('Install "cryptography" Python package to use Flet security utils.') +except ImportError as e: + raise Exception( + 'Install "cryptography" Python package to use Flet security utils.' + ) from e def __generate_fernet_key(secret_key: str) -> bytes: diff --git a/sdk/python/packages/flet/src/flet/utils/__init__.py b/sdk/python/packages/flet/src/flet/utils/__init__.py index ce8d09f0d..44b72c3ed 100644 --- a/sdk/python/packages/flet/src/flet/utils/__init__.py +++ b/sdk/python/packages/flet/src/flet/utils/__init__.py @@ -1,6 +1,6 @@ from flet.utils.browser import open_in_browser from flet.utils.classproperty import classproperty -from flet.utils.deprecated import deprecated +from flet.utils.deprecated import deprecated, deprecated_class, deprecated_warning from flet.utils.files import ( cleanup_path, copy_tree, @@ -9,8 +9,11 @@ safe_tar_extractall, which, ) +from flet.utils.from_dict import from_dict from flet.utils.hashing import calculate_file_hash, sha1 +from flet.utils.json_utils import to_json from flet.utils.network import get_free_tcp_port, get_local_ip +from flet.utils.object_model import get_param_count, patch_dataclass from flet.utils.once import Once from flet.utils.platform_utils import ( get_arch, @@ -30,3 +33,42 @@ from flet.utils.slugify import slugify from flet.utils.strings import random_string from flet.utils.vector import Vector + +__all__ = [ + "open_in_browser", + "classproperty", + "deprecated", + "deprecated_class", + "deprecated_warning", + "cleanup_path", + "copy_tree", + "get_current_script_dir", + "is_within_directory", + "safe_tar_extractall", + "which", + "from_dict", + "calculate_file_hash", + "sha1", + "to_json", + "get_free_tcp_port", + "get_local_ip", + "Once", + "patch_dataclass", + "get_arch", + "get_bool_env_var", + "get_platform", + "is_android", + "is_asyncio", + "is_embedded", + "is_ios", + "is_linux", + "is_linux_server", + "is_macos", + "is_mobile", + "is_pyodide", + "is_windows", + "slugify", + "random_string", + "Vector", + "get_param_count", +] diff --git a/sdk/python/packages/flet/src/flet/utils/deprecated.py b/sdk/python/packages/flet/src/flet/utils/deprecated.py index 4734dcb01..a1a233d69 100644 --- a/sdk/python/packages/flet/src/flet/utils/deprecated.py +++ b/sdk/python/packages/flet/src/flet/utils/deprecated.py @@ -2,29 +2,42 @@ import warnings from typing import Optional +__all__ = ["deprecated", "deprecated_class", "deprecated_warning"] -def deprecated(reason: str, version: str, delete_version: str, is_method=True): + +def deprecated( + reason: str, + version: Optional[str] = None, + delete_version: Optional[str] = None, + show_parentheses: bool = False, +): """ - A decorator function that marks a function/method/property/event as deprecated. + A decorator that marks a function, method, or class as deprecated. :param reason: The reason for deprecation. - :param version: The version from which the function was deprecated. - :param delete_version: The version in which the function will be removed from the API. - :param is_method: if the deprecated item is a method (True) or property/function/event (False) + :param version: (Optional) The version from which the function was deprecated. + :param delete_version: (Optional) The version in which the function will be removed. + :param show_parentheses: Whether to show parentheses after the function/class name in the warning. """ def decorator(func): @functools.wraps(func) - def new_func(*args, **kwargs): + def wrapper(*args, **kwargs): + msg = f"{func.__name__}{'()' if show_parentheses else ''} is deprecated" + if version: + msg += f" since version {version}" + if delete_version: + msg += f" and will be removed in version {delete_version}" + msg += f". {reason}" + warnings.warn( - f"{func.__name__}{'()' if is_method else ''} is deprecated since version {version} " - f"and will be removed in version {delete_version}. {reason}", + msg, category=DeprecationWarning, stacklevel=2, ) return func(*args, **kwargs) - return new_func + return wrapper return decorator @@ -47,11 +60,24 @@ def new_init(self, *args, **kwargs): return decorator -def deprecated_property( - name: str, reason: str, version: str, delete_version: Optional[str] = None +def deprecated_warning( + name: str, + reason: str, + version: str, + delete_version: Optional[str] = None, + type: str = "property", ): + """ + Helper function to issue a standardized deprecation warning message. + + :param name: The name of the deprecated object. + :param reason: A short explanation of why the object is deprecated and/or what to use instead. + :param version: The version in which the object was marked as deprecated. + :param delete_version: Optional; the version in which the object is scheduled to be removed. + :param type: The type of the object being deprecated (e.g., "property"). Defaults to "property". + """ warnings.warn( - f"{name} property is deprecated since version {version}" + f"{name} {type} is deprecated since version {version}" f"{' and will be removed in version ' + delete_version if delete_version else ''}. {reason}", category=DeprecationWarning, stacklevel=2, diff --git a/sdk/python/packages/flet/src/flet/utils/from_dict.py b/sdk/python/packages/flet/src/flet/utils/from_dict.py new file mode 100644 index 000000000..571b6609c --- /dev/null +++ b/sdk/python/packages/flet/src/flet/utils/from_dict.py @@ -0,0 +1,126 @@ +import dataclasses +import sys +from enum import Enum +from typing import ( + Any, + ForwardRef, + TypeVar, + Union, + _eval_type, + get_args, + get_origin, + get_type_hints, +) + +T = TypeVar("T") + + +def from_dict(cls: type[T], data: Any) -> T: + # Handle generic types and ForwardRefs + origin = get_origin(cls) or cls + args = get_args(cls) + + # If cls is a generic like Event[T], resolve T + if args: + cls = origin # drop the generic info; dataclasses only apply to the base class + + # If cls is a ForwardRef, resolve it + if isinstance(cls, ForwardRef): + globalns = sys.modules[cls.__module__].__dict__ + cls = _eval_type(cls, globalns, None) + + if dataclasses.is_dataclass(cls): + try: + type_hints = get_type_hints( + cls, globalns=sys.modules[cls.__module__].__dict__ + ) + except Exception: + type_hints = {f.name: f.type for f in dataclasses.fields(cls)} # fallback + + init_values = {} + post_values = {} + + for field in dataclasses.fields(cls): + field_name = field.name + field_type = type_hints.get(field_name, field.type) + data_field_name = field.metadata.get("data_field", field_name) + + if data_field_name in data: + value = data[data_field_name] + converted = convert_value(field_type, value) + init_values[field_name] = converted + + # set _prev_* values + post_values[f"_prev_{field_name}"] = converted + + # First create the object using init-only fields + instance = cls(**init_values) + + # Now set the _prev_* fields via setattr (won’t raise errors + # if they're not declared) + for k, v in post_values.items(): + setattr(instance, k, v) + + return instance + + else: + return convert_value(cls, data) + + +def convert_value(field_type: type, value: Any) -> Any: + """ + Converts a value to its appropriate type based on the field_type. + Handles nested dataclasses, enums, lists, dicts, and optionals. + + Args: + field_type: The type to convert the value to. + value: The value to convert. + + Returns: + The converted value. + """ + origin = get_origin(field_type) + args = get_args(field_type) + + # Optional[T] + if origin is Union and type(None) in args: + inner_type = [arg for arg in args if arg is not type(None)][0] + if value is None: + return None + return convert_value(inner_type, value) + + # Enum + if isinstance(field_type, type) and issubclass(field_type, Enum): + return field_type(value) + + # Dataclass + if dataclasses.is_dataclass(field_type) and isinstance(value, dict): + return from_dict(field_type, value) + + # List[T] + if origin is list and isinstance(value, list): + item_type = args[0] + return [convert_value(item_type, item) for item in value] + + # Dict[K, V] + if origin is dict and isinstance(value, dict): + key_type, val_type = args + return { + convert_value(key_type, k): convert_value(val_type, v) + for k, v in value.items() + } + + return value # literal + + +def is_literal(value: Any) -> bool: + """ + Checks if a value is a basic literal (int, float, str, bool, or None). + + Args: + value: The value to check. + + Returns: + True if the value is a literal type; False otherwise. + """ + return isinstance(value, (int, float, str, bool, type(None))) diff --git a/sdk/python/packages/flet/src/flet/utils/json_utils.py b/sdk/python/packages/flet/src/flet/utils/json_utils.py new file mode 100644 index 000000000..e301a882c --- /dev/null +++ b/sdk/python/packages/flet/src/flet/utils/json_utils.py @@ -0,0 +1,12 @@ +import json +from typing import Optional + +from flet.controls.embed_json_encoder import EmbedJsonEncoder + + +def to_json(value) -> Optional[str]: + return ( + json.dumps(value, cls=EmbedJsonEncoder, separators=(",", ":")) + if value is not None + else None + ) diff --git a/sdk/python/packages/flet/src/flet/core/locks.py b/sdk/python/packages/flet/src/flet/utils/locks.py similarity index 100% rename from sdk/python/packages/flet/src/flet/core/locks.py rename to sdk/python/packages/flet/src/flet/utils/locks.py diff --git a/sdk/python/packages/flet/src/flet/utils/object_model.py b/sdk/python/packages/flet/src/flet/utils/object_model.py new file mode 100644 index 000000000..62af55ee3 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/utils/object_model.py @@ -0,0 +1,80 @@ +import dataclasses +import inspect +import sys +from enum import Enum +from typing import Any, Union, get_args, get_origin, get_type_hints + +from flet.utils.from_dict import from_dict + + +def patch_dataclass(obj: Any, patch: dict): + cls = obj.__class__ + + try: + frame = inspect.currentframe().f_back + globalns = sys.modules[cls.__module__].__dict__ + localns = frame.f_globals.copy() + localns.update(frame.f_locals) + hints = get_type_hints(cls, globalns=globalns, localns=localns) + except Exception: + hints = {f.name: f.type for f in dataclasses.fields(cls)} # fallback + # print("ERROR: ", cls.__name__, e) + + for field_name, value in patch.items(): + if field_name in hints: + field_type = hints[field_name] + current_value = getattr(obj, field_name, None) + actual_type = resolve_actual_type(field_type) + + if isinstance(actual_type, str): + continue # unresolved forward ref + + # Nested dataclass patching + if dataclasses.is_dataclass(actual_type) and isinstance(value, dict): + if current_value is None: + object.__setattr__(obj, field_name, from_dict(actual_type, value)) + else: + patch_dataclass(current_value, value) + + # List of dataclasses or values + elif get_origin(actual_type) is list and isinstance(value, list): + item_type = get_args(actual_type)[0] + if dataclasses.is_dataclass(item_type): + object.__setattr__( + obj, field_name, [from_dict(item_type, item) for item in value] + ) + else: + object.__setattr__(obj, field_name, value) + + # Enum + elif is_enum(actual_type): + enum_value = actual_type(value) + object.__setattr__(obj, field_name, enum_value) + + # Simple literal or other value + else: + object.__setattr__(obj, field_name, value) + elif field_name.startswith("_"): + setattr(obj, field_name, value) + + +def resolve_actual_type(tp: Any) -> Any: + origin = get_origin(tp) + args = get_args(tp) + + if origin is Union and len(args) == 2 and type(None) in args: + # It's Optional[T] + return args[0] if args[1] is type(None) else args[1] + + return tp + + +def is_enum(tp: Any) -> bool: + return isinstance(tp, type) and issubclass(tp, Enum) + + +def get_param_count(fn): + try: + return len(inspect.signature(fn).parameters) + except (ValueError, TypeError): + return None diff --git a/sdk/python/packages/flet/src/flet/version.py b/sdk/python/packages/flet/src/flet/version.py index f3a8f0a4d..d69895ad5 100644 --- a/sdk/python/packages/flet/src/flet/version.py +++ b/sdk/python/packages/flet/src/flet/version.py @@ -7,52 +7,68 @@ import flet from flet.utils import is_mobile, is_windows, which +DEFAULT_VERSION = "0.1.0" + # will be replaced by CI version = "" def update_version(): - """Return the current version or default.""" + """Try to get the version from Git tags.""" working = Path().absolute() - os.chdir(Path(flet.__file__).absolute().parent) - in_repo = ( - which("git.exe" if is_windows() else "git") - and sp.run( - ["git", "status"], - capture_output=True, - text=True, - ).returncode - == 0 - ) - - if in_repo: - # NOTE: this may break if there is a tag name starting with - # "v" that isn't a version number - class RepositoryError(OSError): - pass - - git_p = sp.run( - ["git", "describe", "--abbrev=0"], - capture_output=True, - text=True, - ) - err = git_p.stderr.strip() - - if "cannot describe anything" in err: - msg = "You may be using a repo cloned from a fork. " - msg += "If so please clone the original Flet repo" - raise RepositoryError(msg) - - if err: - msg = "Unknown error while fetching the version: {err}" - raise RepositoryError(msg) - version = git_p.stdout.strip()[1:] - - else: - version = "0.2.0" - os.chdir(working) - return version + try: + version_file_path = Path(flet.__file__).absolute().parent / "version.py" + repo_root = find_repo_root(version_file_path.parent) + + if repo_root: + os.chdir(repo_root) + in_repo = ( + which("git.exe" if is_windows() else "git") + and sp.run( + ["git", "status"], + capture_output=True, + text=True, + ).returncode + == 0 + ) + + if in_repo: + try: + git_p = sp.run( + ["git", "describe", "--abbrev=0"], + capture_output=True, + text=True, + check=True, # Raise an exception for non-zero exit codes + ) + tag = git_p.stdout.strip() + return tag[1:] if tag.startswith("v") else tag + except sp.CalledProcessError as e: + print(f"Error getting Git version: {e}") + except FileNotFoundError: + print("Git command not found.") + finally: + os.chdir(working) + return None + + +def find_repo_root(start_path: Path) -> Path | None: + """Find the root directory of the Git repository containing the start path.""" + current_path = start_path.resolve() + while current_path != current_path.parent: + if (current_path / ".git").is_dir(): + return current_path + current_path = current_path.parent + return None if not version and not is_mobile(): - version = update_version() + # Only try to get the version from Git if the pre-set version is empty + # This is more likely to happen in a development/source environment + version = update_version() or DEFAULT_VERSION # Fallback to a default if Git fails + +# If 'version' is still empty after the above (e.g., in a built package +# where CI didn't replace it), it might be appropriate to have another +# default or a way to set it during the build process. However, the +# CI replacement is the standard way for packaged versions. +if not version: + version = DEFAULT_VERSION # Final fallback diff --git a/sdk/python/packages/flet/tests/common.py b/sdk/python/packages/flet/tests/common.py new file mode 100644 index 000000000..b8f90ef0c --- /dev/null +++ b/sdk/python/packages/flet/tests/common.py @@ -0,0 +1,116 @@ +import datetime +from dataclasses import field +from typing import Any, Optional + +import flet as ft +import msgpack + +# import flet as ft +# import flet.canvas as cv +from flet.controls.object_patch import ObjectPatch +from flet.messaging.protocol import configure_encode_object_for_msgpack + + +@ft.control("LineChartDataPoint") +class LineChartDataPoint(ft.BaseControl): + x: ft.Number + y: ft.Number + selected: bool = False + + +@ft.control("LineChartData") +class LineChartData(ft.BaseControl): + points: list[LineChartDataPoint] = field(default_factory=list) + curved: bool = False + color: ft.ColorValue = ft.Colors.CYAN + gradient: Optional[ft.Gradient] = None + + +@ft.control("LineChart") +class LineChart(ft.ConstrainedControl): + data_series: list[LineChartData] = field(default_factory=list) + animation: ft.AnimationValue = field( + default_factory=lambda: ft.Animation( + duration=ft.Duration(milliseconds=150), curve=ft.AnimationCurve.LINEAR + ) + ) + interactive: bool = True + _skip_inherited_notifier: Optional[bool] = None + + def __post_init__(self, ref: Optional[ft.Ref[Any]]): + super().__post_init__(ref) + self._internals["skip_properties"] = ["tooltip"] + + def init(self): + super().init() + self._skip_inherited_notifier = True + + +def b_pack(data): + return msgpack.packb( + data, default=configure_encode_object_for_msgpack(ft.BaseControl) + ) + + +def b_unpack(packed_data): + return msgpack.unpackb(packed_data) + + +def make_diff(new: Any, old: Any = None, show_details=True): + if old is None: + old = new + start = datetime.datetime.now() + + # 1 -calculate diff + patch, added_controls, removed_controls = ObjectPatch.from_diff( + old, new, control_cls=ft.BaseControl + ) + + patch_message = patch.to_message() + + end = datetime.datetime.now() + + if show_details: + print(f"\n=== Patch in {(end - start).total_seconds() * 1000} ms ===") + for op in patch.patch: + print(op) + print("\n=== Patch message:", patch_message) + + return patch.patch, patch_message, added_controls, removed_controls + + +def make_msg(new: Any, old: Any = None, show_details=True): + patch, patch_message, added_controls, removed_controls = make_diff( + new, old, show_details + ) + + # 3 - build msgpack message + msg = msgpack.packb( + patch_message, default=configure_encode_object_for_msgpack(ft.BaseControl) + ) + + if show_details: + print("\nMessage:", msg) + else: + print("\nMessage length:", len(msg)) + + return msg, patch, patch_message, added_controls, removed_controls + + +def cmp_op(op, cop): + return not ( + (cop["op"] is not None and op["op"] != cop["op"]) + or (cop["path"] is not None and op["path"] != cop["path"]) + or ("from" in cop and cop["from"] is not None and op["from"] != cop["from"]) + or ("value" in cop and cop["value"] is not None and op["value"] != cop["value"]) + or ( + "value_type" in cop + and cop["value_type"] is not None + and not isinstance(op["value"], cop["value_type"]) + ) + ) + + +def cmp_ops(ops, cops): + assert len(ops) == len(cops) + return all(cmp_op(ops[i], cops[i]) for i in range(0, len(ops))) diff --git a/sdk/python/packages/flet/tests/test_alert_dialog.py b/sdk/python/packages/flet/tests/test_alert_dialog.py deleted file mode 100644 index 55ef23a6b..000000000 --- a/sdk/python/packages/flet/tests/test_alert_dialog.py +++ /dev/null @@ -1,41 +0,0 @@ -import flet as ft -from flet.core.protocol import Command - - -def test_instance_no_attrs_set(): - r = ft.AlertDialog(title=ft.Text("Title")) - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["alertdialog"], - attrs={"modal": "false", "open": "false"}, - commands=[], - ), - Command( - indent=2, - name=None, - values=["text"], - attrs={"n": "title", "value": "Title"}, - commands=[], - ), - ], "Test failed" - - -def test_alignment_enum(): - r = ft.AlertDialog( - title=ft.Text("Title"), actions_alignment=ft.MainAxisAlignment.SPACE_AROUND - ) - assert isinstance(r.actions_alignment, ft.MainAxisAlignment) - assert isinstance(r._get_attr("actionsAlignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["actionsalignment"] == "spaceAround" - - -def test_alignment_str(): - r = ft.AlertDialog(title=ft.Text("Title"), actions_alignment="center") - assert isinstance(r.actions_alignment, str) - assert isinstance(r._get_attr("actionsalignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["actionsalignment"] == "center" diff --git a/sdk/python/packages/flet/tests/test_animated_switcher.py b/sdk/python/packages/flet/tests/test_animated_switcher.py deleted file mode 100644 index 8ec4ba356..000000000 --- a/sdk/python/packages/flet/tests/test_animated_switcher.py +++ /dev/null @@ -1,74 +0,0 @@ -import flet as ft -from flet.core.protocol import Command - - -def test_instance_no_attrs_set(): - r = ft.AnimatedSwitcher(content=ft.Text("Hello!")) - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["animatedswitcher"], - attrs={}, - commands=[], - ), - Command( - indent=2, - name=None, - values=["text"], - attrs={"n": "content", "value": "Hello!"}, - commands=[], - ), - ], "Test failed" - - -def test_switch_in_curve_enum(): - r = ft.AnimatedSwitcher(content=ft.Text("Hello!")) - assert r.switch_in_curve is None - assert r._get_attr("switchInCurve") is None - - r = ft.AnimatedSwitcher( - content=ft.Text("Hello!"), switch_in_curve=ft.AnimationCurve.BOUNCE_IN - ) - assert isinstance(r.switch_in_curve, ft.AnimationCurve) - assert r.switch_in_curve == ft.AnimationCurve.BOUNCE_IN - assert r._get_attr("switchInCurve") == "bounceIn" - - r = ft.AnimatedSwitcher(content=ft.Text("Hello!"), switch_in_curve="easeIn") - assert isinstance(r.switch_in_curve, str) - assert r._get_attr("switchInCurve") == "easeIn" - - -def test_switch_out_curve_enum(): - r = ft.AnimatedSwitcher(content=ft.Text("Hello!")) - assert r.switch_out_curve is None - assert r._get_attr("switchOutCurve") is None - - r = ft.AnimatedSwitcher( - content=ft.Text("Hello!"), switch_out_curve=ft.AnimationCurve.BOUNCE_IN - ) - assert isinstance(r.switch_out_curve, ft.AnimationCurve) - assert r.switch_out_curve == ft.AnimationCurve.BOUNCE_IN - assert r._get_attr("switchOutCurve") == "bounceIn" - - r = ft.AnimatedSwitcher(content=ft.Text("Hello!"), switch_out_curve="easeIn") - assert isinstance(r.switch_out_curve, str) - assert r._get_attr("switchOutCurve") == "easeIn" - - -def test_transition_enum(): - r = ft.AnimatedSwitcher(content=ft.Text("Hello!")) - assert r.transition is None - assert r._get_attr("transition") is None - - r = ft.AnimatedSwitcher( - content=ft.Text("Hello!"), transition=ft.AnimatedSwitcherTransition.FADE - ) - assert isinstance(r.transition, ft.AnimatedSwitcherTransition) - assert r.transition == ft.AnimatedSwitcherTransition.FADE - assert r._get_attr("transition") == "fade" - - r = ft.AnimatedSwitcher(content=ft.Text("Hello!"), transition="scale") - assert isinstance(r.transition, str) - assert r._get_attr("transition") == "scale" diff --git a/sdk/python/packages/flet/tests/test_base_control.py b/sdk/python/packages/flet/tests/test_base_control.py new file mode 100644 index 000000000..70001e8ad --- /dev/null +++ b/sdk/python/packages/flet/tests/test_base_control.py @@ -0,0 +1,33 @@ +import flet as ft + + +def test_controls_equality(): + t1 = ft.Text("A") + t2 = ft.Text("A") + assert t1 == t2 + + t3 = ft.Text("B", data=1) + t4 = ft.Text("B", data=2) + assert t3 != t4 + + c1 = ft.Column( + [ft.Text("Some text"), ft.Button("Some button")], expand=True, data=1 + ) + c2 = ft.Column( + [ft.Text("Some text"), ft.Button("Some button")], expand=True, data=1 + ) + assert c1 == c2 + + +def test_keys(): + k1 = ft.ValueKey(1) + assert str(k1) == "1" + assert k1._type == "value" + + k2 = ft.ScrollKey("section_a") + assert str(k2) == "section_a" + assert k2._type == "scroll" + + t1 = ft.Text("A", key=ft.ValueKey("1")) + t2 = ft.Text("A", key=ft.ValueKey("1")) + assert t1 == t2 diff --git a/sdk/python/packages/flet/tests/test_checkbox.py b/sdk/python/packages/flet/tests/test_checkbox.py deleted file mode 100644 index d902c76cb..000000000 --- a/sdk/python/packages/flet/tests/test_checkbox.py +++ /dev/null @@ -1,33 +0,0 @@ -from flet.core.protocol import Command - -import flet as ft - - -def test_instance_no_attrs_set(): - r = ft.Checkbox() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["checkbox"], - attrs={}, - commands=[], - ) - ], "Test failed" - - -def test_label_position_enum(): - r = ft.Checkbox(label_position=ft.LabelPosition.LEFT) - assert isinstance(r.label_position, ft.LabelPosition) - assert isinstance(r._get_attr("labelPosition"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["labelposition"] == "left" - - -def test_label_position_str(): - r = ft.Checkbox(label_position="left") - assert isinstance(r.label_position, str) - assert isinstance(r._get_attr("labelPosition"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["labelposition"] == "left" diff --git a/sdk/python/packages/flet/tests/test_column.py b/sdk/python/packages/flet/tests/test_column.py deleted file mode 100644 index 863ddeaa0..000000000 --- a/sdk/python/packages/flet/tests/test_column.py +++ /dev/null @@ -1,72 +0,0 @@ -from flet.core.protocol import Command - -import flet as ft - - -def test_instance_no_attrs_set(): - r = ft.Column() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["column"], - attrs={}, - commands=[], - ) - ], "Test failed" - - -def test_alignment_enum(): - r = ft.Column(alignment=ft.MainAxisAlignment.SPACE_AROUND) - assert isinstance(r.alignment, ft.MainAxisAlignment) - assert isinstance(r._get_attr("alignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["alignment"] == "spaceAround" - - -def test_alignment_str(): - r = ft.Column(alignment="center") - assert isinstance(r.alignment, str) - assert isinstance(r._get_attr("alignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["alignment"] == "center" - - -def test_horizontal_alignment_enum(): - r = ft.Column(horizontal_alignment=ft.CrossAxisAlignment.STRETCH) - assert isinstance(r.horizontal_alignment, ft.CrossAxisAlignment) - assert isinstance(r._get_attr("horizontalAlignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["horizontalalignment"] == "stretch" - - -def test_horizontal_alignment_str(): - r = ft.Column(horizontal_alignment="center") - assert isinstance(r.horizontal_alignment, str) - assert isinstance(r._get_attr("horizontalAlignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["horizontalalignment"] == "center" - - -def test_scroll_enum(): - r = ft.Column() - assert r.scroll is None - assert r._get_attr("scroll") is None - - r = ft.Column(scroll=ft.ScrollMode.ALWAYS) - assert isinstance(r.scroll, ft.ScrollMode) - assert r.scroll == ft.ScrollMode.ALWAYS - assert r._get_attr("scroll") == "always" - - r = ft.Column(scroll="adaptive") - assert isinstance(r.scroll, str) - assert r._get_attr("scroll") == "adaptive" - - r = ft.Column(scroll=True) - assert isinstance(r.scroll, bool) - assert r._get_attr("scroll") == "auto" - - r = ft.Column(scroll=False) - assert isinstance(r.scroll, bool) - assert r._get_attr("scroll") is None diff --git a/sdk/python/packages/flet/tests/test_container.py b/sdk/python/packages/flet/tests/test_container.py deleted file mode 100644 index f3ede66aa..000000000 --- a/sdk/python/packages/flet/tests/test_container.py +++ /dev/null @@ -1,95 +0,0 @@ -from flet.core.protocol import Command - -import flet as ft - - -def test_instance_no_attrs_set(): - r = ft.Container() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["container"], - attrs={}, - commands=[], - ) - ], "Test failed" - - -def test_gradient(): - c = ft.Container( - gradient=ft.LinearGradient( - colors=[], - tile_mode=ft.GradientTileMode.MIRROR, - ) - ) - cmd = c._build_add_commands() - assert ( - cmd[0].attrs["gradient"] - == '{"colors":[],"tile_mode":"mirror","begin":{"x":-1,"y":0},"end":{"x":1,"y":0},"type":"linear"}' - ) - - c = ft.Container( - gradient=ft.LinearGradient( - colors=[], - tile_mode=ft.GradientTileMode.REPEATED, - ) - ) - cmd = c._build_add_commands() - assert ( - cmd[0].attrs["gradient"] - == '{"colors":[],"tile_mode":"repeated","begin":{"x":-1,"y":0},"end":{"x":1,"y":0},"type":"linear"}' - ) - - c = ft.Container( - gradient=ft.LinearGradient( - colors=[], - ) - ) - cmd = c._build_add_commands() - assert ( - cmd[0].attrs["gradient"] - == '{"colors":[],"tile_mode":"clamp","begin":{"x":-1,"y":0},"end":{"x":1,"y":0},"type":"linear"}' - ) - - -def test_blend_mode_enum(): - r = ft.Container(blend_mode=ft.BlendMode.LIGHTEN, bgcolor=ft.Colors.RED) - assert isinstance(r.blend_mode, ft.BlendMode) - assert isinstance(r._get_attr("blendMode"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["blendmode"] == "lighten" - - -def test_clip_behavior_enum(): - r = ft.Container() - assert r.clip_behavior is None - assert r._get_attr("clipBehavior") is None - - r = ft.Container(clip_behavior=ft.ClipBehavior.ANTI_ALIAS) - assert isinstance(r.clip_behavior, ft.ClipBehavior) - assert r.clip_behavior == ft.ClipBehavior.ANTI_ALIAS - assert r._get_attr("clipBehavior") == "antiAlias" - - r = ft.Container(clip_behavior=ft.ClipBehavior.NONE) - assert isinstance(r.clip_behavior, ft.ClipBehavior) - assert r._get_attr("clipBehavior") == "none" - - -def test_image_repeat_enum(): - r = ft.Container() - assert r.image is None - - r = ft.Container(image=ft.DecorationImage(repeat=ft.ImageRepeat.REPEAT)) - assert isinstance(r.image.repeat, ft.ImageRepeat) - assert r.image.repeat == ft.ImageRepeat.REPEAT - - -def test_image_fit_enum(): - r = ft.Container() - assert r.image is None - - r = ft.Container(image=ft.DecorationImage(fit=ft.ImageFit.FILL)) - assert isinstance(r.image.fit, ft.ImageFit) - assert r.image.fit == ft.ImageFit.FILL diff --git a/sdk/python/packages/flet/tests/test_dataclasses.py b/sdk/python/packages/flet/tests/test_dataclasses.py new file mode 100644 index 000000000..69efd335e --- /dev/null +++ b/sdk/python/packages/flet/tests/test_dataclasses.py @@ -0,0 +1,19 @@ +from dataclasses import dataclass, field, fields + +import flet as ft + + +@dataclass +class Foo: + prop_a: str = "default_a" + prop_b: str = field(default="default_b") + + +def test_default_values(): + foo = Foo() + assert foo.prop_a == "default_a" + + foo_fields = fields(foo) + assert len(foo_fields) == 2 + assert foo_fields[0].default == "default_a" + assert foo_fields[1].default == "default_b" diff --git a/sdk/python/packages/flet/tests/test_datatable.py b/sdk/python/packages/flet/tests/test_datatable.py deleted file mode 100644 index 1b0864576..000000000 --- a/sdk/python/packages/flet/tests/test_datatable.py +++ /dev/null @@ -1,99 +0,0 @@ -from flet.core.protocol import Command - -import flet as ft - - -def test_datatable_instance_no_attrs_set(): - r = ft.DataTable(columns=[ft.DataColumn(label=ft.Text("Header"))]) - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command(indent=0, name=None, values=["datatable"], attrs={}, commands=[]), - Command(indent=2, name=None, values=["datacolumn"], attrs={}, commands=[]), - Command( - indent=4, - name=None, - values=["text"], - attrs={"n": "label", "value": "Header"}, - commands=[], - ), - ], "Test failed" - - -def test_datarow_instance_no_attrs_set(): - r = ft.DataRow(cells=[ft.DataCell(content=ft.Text("Cell"))]) - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command(indent=0, name=None, values=["datarow"], attrs={}, commands=[]), - Command(indent=2, name=None, values=["datacell"], attrs={}, commands=[]), - Command( - indent=4, name=None, values=["text"], attrs={"value": "Cell"}, commands=[] - ), - ], "Test failed" - - -def test_datarow_color_literal_material_state_as_string(): - r = ft.DataRow(cells=[ft.DataCell(content=ft.Text("Cell"))], color="yellow") - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["datarow"], - attrs={"color": '{"default":"yellow"}'}, - commands=[], - ), - Command(indent=2, name=None, values=["datacell"], attrs={}, commands=[]), - Command( - indent=4, name=None, values=["text"], attrs={"value": "Cell"}, commands=[] - ), - ], "Test failed" - - -def test_datarow_color_multiple_material_states_as_strings(): - r = ft.DataRow( - cells=[ft.DataCell(content=ft.Text("Cell"))], - color={ - ft.ControlState.SELECTED: "red", - ft.ControlState.HOVERED: "blue", - ft.ControlState.DEFAULT: "yellow", - }, - ) - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["datarow"], - attrs={"color": '{"selected":"red","hovered":"blue","default":"yellow"}'}, - commands=[], - ), - Command(indent=2, name=None, values=["datacell"], attrs={}, commands=[]), - Command( - indent=4, name=None, values=["text"], attrs={"value": "Cell"}, commands=[] - ), - ], "Test failed" - - -def test_datarow_color_multiple_material_states(): - r = ft.DataRow( - cells=[ft.DataCell(content=ft.Text("Cell"))], - color={ - ft.ControlState.SELECTED: "red", - ft.ControlState.HOVERED: "blue", - ft.ControlState.DEFAULT: "yellow", - }, - ) - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["datarow"], - attrs={"color": '{"selected":"red","hovered":"blue","default":"yellow"}'}, - commands=[], - ), - Command(indent=2, name=None, values=["datacell"], attrs={}, commands=[]), - Command( - indent=4, name=None, values=["text"], attrs={"value": "Cell"}, commands=[] - ), - ], "Test failed" diff --git a/sdk/python/packages/flet/tests/test_datetime.py b/sdk/python/packages/flet/tests/test_datetime.py new file mode 100644 index 000000000..15723d3bf --- /dev/null +++ b/sdk/python/packages/flet/tests/test_datetime.py @@ -0,0 +1,10 @@ +import datetime + +import flet as ft + + +def test_localdate_serialize(): + dt = datetime.datetime(year=2024, month=1, day=20) + print("\nNaive:", dt.isoformat()) + print("Local:", dt.astimezone().isoformat()) + print("UTC:", dt.astimezone().astimezone(datetime.timezone.utc).isoformat()) diff --git a/sdk/python/packages/flet/tests/test_dropdown.py b/sdk/python/packages/flet/tests/test_dropdown.py deleted file mode 100644 index 768ff1860..000000000 --- a/sdk/python/packages/flet/tests/test_dropdown.py +++ /dev/null @@ -1,31 +0,0 @@ -import flet as ft -from flet.core.protocol import Command - - -def test_instance_no_attrs_set(): - r = ft.Dropdown() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["dropdown"], - attrs={}, - commands=[], - ) - ], "Test failed" - - -def test_border_enum(): - r = ft.Dropdown() - assert r.border is None - assert r._get_attr("border") is None - - r = ft.Dropdown(border=ft.InputBorder.OUTLINE) - assert isinstance(r.border, ft.InputBorder) - assert r.border == ft.InputBorder.OUTLINE - assert r._get_attr("border") == "outline" - - r = ft.Dropdown(border="none") - assert isinstance(r.border, str) - assert r._get_attr("border") == "none" diff --git a/sdk/python/packages/flet/tests/test_events.py b/sdk/python/packages/flet/tests/test_events.py new file mode 100644 index 000000000..99f50c4de --- /dev/null +++ b/sdk/python/packages/flet/tests/test_events.py @@ -0,0 +1,103 @@ +from typing import ForwardRef, get_args, get_origin + +from flet.controls.control_event import ControlEvent, Event +from flet.controls.core.column import Column +from flet.controls.events import TapEvent +from flet.controls.material.container import Container +from flet.controls.material.elevated_button import ElevatedButton +from flet.controls.material.reorderable_list_view import ( + OnReorderEvent, + ReorderableListView, +) +from flet.controls.page import Page +from flet.controls.page_view import PageResizeEvent +from flet.controls.scrollable_control import OnScrollEvent +from flet.messaging.connection import Connection +from flet.messaging.session import Session +from flet.pubsub.pubsub_hub import PubSubHub +from flet.utils.from_dict import from_dict + + +def test_get_event_field_type(): + btn = ElevatedButton() + on_click_type = ControlEvent.get_event_field_type(btn, "on_click") + assert get_origin(on_click_type) is Event + assert get_args(on_click_type)[0] == ForwardRef("ElevatedButton") + + c = Container() + on_tap_down_type = ControlEvent.get_event_field_type(c, "on_tap_down") + assert on_tap_down_type == TapEvent["Container"] + assert on_tap_down_type != ControlEvent + + col = Column() + on_scroll_type = ControlEvent.get_event_field_type(col, "on_scroll") + assert on_scroll_type == OnScrollEvent + assert on_scroll_type != ControlEvent + + +def test_create_event_typed_data(): + c = Container() + on_tap_down_type = ControlEvent.get_event_field_type(c, "on_tap_down") + assert on_tap_down_type == TapEvent["Container"] + + evt = from_dict( + on_tap_down_type, + { + "control": c, + "name": "some_event", + "data": None, + "lx": 1, + "ly": 2, + "gx": 4, + "gy": 5, + }, + ) + + assert isinstance(evt, TapEvent) + assert evt.local_x == 1 + assert evt.global_y == 5 + assert evt.control == c + assert evt.name == "some_event" + + +def test_create_reorder_event(): + c = ReorderableListView() + on_reorder_type = ControlEvent.get_event_field_type(c, "on_reorder") + assert on_reorder_type == OnReorderEvent + + evt = from_dict( + on_reorder_type, + { + "control": c, + "name": "some_event", + "data": None, + "old_index": 0, + "new_index": 1, + }, + ) + + assert isinstance(evt, OnReorderEvent) + assert evt.old_index == 0 + assert evt.new_index == 1 + assert evt.control == c + assert evt.name == "some_event" + + +def test_page_events(): + conn = Connection() + conn.pubsubhub = PubSubHub() + p = Page(sess=Session(conn)) + on_resized_type = ControlEvent.get_event_field_type(p, "on_resized") + assert on_resized_type == PageResizeEvent + evt = from_dict( + on_resized_type, + { + "control": p, + "name": "on_resized", + "data": None, + "width": 1, + "height": 2, + }, + ) + + assert isinstance(evt, PageResizeEvent) diff --git a/sdk/python/packages/flet/tests/test_file_picker.py b/sdk/python/packages/flet/tests/test_file_picker.py deleted file mode 100644 index b15abb062..000000000 --- a/sdk/python/packages/flet/tests/test_file_picker.py +++ /dev/null @@ -1,30 +0,0 @@ -from flet.core.protocol import Command - -import flet as ft - - -def test_instance_no_attrs_set(): - r = ft.FilePicker() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["filepicker"], - attrs={"upload": "[]"}, - commands=[], - ) - ], "Test failed" - - -def test_file_type_enum(): - r = ft.FilePicker() - r.file_type = ft.FilePickerFileType.VIDEO - assert isinstance(r.file_type, ft.FilePickerFileType) - assert r.file_type == ft.FilePickerFileType.VIDEO - assert r._get_attr("fileType") == "video" - - r = ft.FilePicker() - r.file_type = "any" - assert isinstance(r.file_type, str) - assert r._get_attr("fileType") == "any" diff --git a/sdk/python/packages/flet/tests/test_from_dict.py b/sdk/python/packages/flet/tests/test_from_dict.py new file mode 100644 index 000000000..45b7e3d7c --- /dev/null +++ b/sdk/python/packages/flet/tests/test_from_dict.py @@ -0,0 +1,63 @@ +from dataclasses import dataclass +from enum import Enum +from typing import Optional + +from flet.controls.padding import Padding +from flet.controls.page_view import PageMediaData +from flet.utils import from_dict + + +def test_page_media_data(): + pm = from_dict( + PageMediaData, + { + "padding": {"left": 1, "top": 2, "right": 3, "bottom": 4}, + "view_padding": {"left": 1, "top": 2, "right": 3, "bottom": 4}, + "view_insets": {"left": 1, "top": 2, "right": 3, "bottom": 4}, + }, + ) + + assert isinstance(pm, PageMediaData) + assert isinstance(pm.padding, Padding) + assert isinstance(pm.view_insets, Padding) + assert isinstance(pm.view_padding, Padding) + assert pm.padding._prev_left == 1 + assert pm.view_insets._prev_top == 2 + + +def test_simple(): + class Status(Enum): + ACTIVE = "active" + INACTIVE = "inactive" + + @dataclass + class Address: + city: str + zip: str + + @dataclass + class User: + name: str + age: Optional[int] + status: Status + address: Optional[Address] + tags: list[str] + metadata: dict[str, int] + + user_data = { + "name": "Alice", + "age": 30, + "status": "active", + "address": {"city": "Springfield", "zip": "12345"}, + "tags": ["admin", "beta"], + "metadata": {"logins": 10, "likes": 42}, + } + + user = from_dict(User, user_data) + assert isinstance(user, User) + assert isinstance(user.address, Address) + assert isinstance(user.status, Status) + assert user._prev_name == "Alice" + assert user._prev_age == 30 + assert user.address._prev_city == "Springfield" + assert user._prev_status == Status.ACTIVE diff --git a/sdk/python/packages/flet/tests/test_image.py b/sdk/python/packages/flet/tests/test_image.py deleted file mode 100644 index 508f26ae2..000000000 --- a/sdk/python/packages/flet/tests/test_image.py +++ /dev/null @@ -1,68 +0,0 @@ -from flet.core.protocol import Command - -import flet as ft - - -def test_image_add(): - i = ft.Image( - src="https://www.w3schools.com/css/img_5terre.jpg", - ) - assert isinstance(i, ft.Control) - assert isinstance(i, ft.Image) - assert i._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["image"], - attrs={ - "src": "https://www.w3schools.com/css/img_5terre.jpg", - }, - commands=[], - ) - ], "Test failed" - - -def test_color_blend_mode_enum(): - r = ft.Image(color_blend_mode=ft.BlendMode.LIGHTEN) - assert isinstance(r.color_blend_mode, ft.BlendMode) - assert isinstance(r._get_attr("colorBlendMode"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["colorblendmode"] == "lighten" - - -def test_color_blend_mode_str(): - r = ft.Image(color_blend_mode="darken") - assert isinstance(r.color_blend_mode, str) - assert isinstance(r._get_attr("colorBlendMode"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["colorblendmode"] == "darken" - - -def test_repeat_enum(): - r = ft.Image() - assert r.repeat is None - assert r._get_attr("repeat") is None - - r = ft.Image(repeat=ft.ImageRepeat.REPEAT) - assert isinstance(r.repeat, ft.ImageRepeat) - assert r.repeat == ft.ImageRepeat.REPEAT - assert r._get_attr("repeat") == "repeat" - - r = ft.Image(repeat="repeatX") - assert isinstance(r.repeat, str) - assert r._get_attr("repeat") == "repeatX" - - -def test_fit_enum(): - r = ft.Image() - assert r.fit is None - assert r._get_attr("fit") is None - - r = ft.Image(fit=ft.ImageFit.FILL) - assert isinstance(r.fit, ft.ImageFit) - assert r.fit == ft.ImageFit.FILL - assert r._get_attr("fit") == "fill" - - r = ft.Image(fit="none") - assert isinstance(r.fit, str) - assert r._get_attr("fit") == "none" diff --git a/sdk/python/packages/flet/tests/test_markdown.py b/sdk/python/packages/flet/tests/test_markdown.py deleted file mode 100644 index 03d4250f0..000000000 --- a/sdk/python/packages/flet/tests/test_markdown.py +++ /dev/null @@ -1,28 +0,0 @@ -from flet.core.protocol import Command - -import flet as ft - - -def test_instance_no_attrs_set(): - r = ft.Markdown() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["markdown"], - attrs={}, - commands=[], - ) - ], "Test failed" - - -def test_extension_set_enum(): - r = ft.Markdown() - assert r.extension_set is None - assert r._get_attr("extensionSet") is None - - r = ft.Markdown(extension_set=ft.MarkdownExtensionSet.COMMON_MARK) - assert isinstance(r.extension_set, ft.MarkdownExtensionSet) - assert r.extension_set == ft.MarkdownExtensionSet.COMMON_MARK - assert r._get_attr("extensionSet") == "commonMark" diff --git a/sdk/python/packages/flet/tests/test_moving_children.py b/sdk/python/packages/flet/tests/test_moving_children.py deleted file mode 100644 index 7132580ef..000000000 --- a/sdk/python/packages/flet/tests/test_moving_children.py +++ /dev/null @@ -1,44 +0,0 @@ -import random - -import flet as ft - - -def test_moving_children(): - c = ft.Stack() - c._Control__uid = "0" - for i in range(0, 10): - c.controls.append(ft.Container()) - c.controls[i]._Control__uid = f"_{i}" - - index = [] - added_controls = [] - removed_controls = [] - commands = [] - c.build_update_commands(index, commands, added_controls, removed_controls, False) - - def replace_controls(c): - random.shuffle(c.controls) - commands.clear() - - # print("=======") - r = set() - for ctrl in c.controls: - # print(ctrl._Control__uid) - r.add(ctrl._Control__uid) - c.build_update_commands( - index, commands, added_controls, removed_controls, False - ) - for cmd in commands: - if cmd.name == "add": - for sub_cmd in cmd.commands: - # print("add", sub_cmd.attrs["id"], "at", cmd.attrs["at"]) - r.add(sub_cmd.attrs["id"]) - elif cmd.name == "remove": - for v in cmd.values: - # print("remove", v) - r.remove(v) - # print(r) - assert len(r) == len(c.controls) - - for i in range(0, 20): - replace_controls(c) diff --git a/sdk/python/packages/flet/tests/test_navigation_bar.py b/sdk/python/packages/flet/tests/test_navigation_bar.py deleted file mode 100644 index 42987bc9d..000000000 --- a/sdk/python/packages/flet/tests/test_navigation_bar.py +++ /dev/null @@ -1,32 +0,0 @@ -from flet.core.protocol import Command - -import flet as ft - - -def test_instance_no_attrs_set(): - r = ft.NavigationBar() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["navigationbar"], - attrs={}, - commands=[], - ) - ], "Test failed" - - -def test_extension_set_enum(): - r = ft.NavigationBar() - assert r.label_behavior is None - assert r._get_attr("labelBehavior") is None - - r = ft.NavigationBar(label_behavior=ft.NavigationBarLabelBehavior.ALWAYS_SHOW) - assert isinstance(r.label_behavior, ft.NavigationBarLabelBehavior) - assert r.label_behavior == ft.NavigationBarLabelBehavior.ALWAYS_SHOW - assert r._get_attr("labelBehavior") == "alwaysShow" - - r = ft.NavigationBar(label_behavior="alwaysHide") - assert isinstance(r.label_behavior, str) - assert r._get_attr("labelBehavior") == "alwaysHide" diff --git a/sdk/python/packages/flet/tests/test_navigation_rail.py b/sdk/python/packages/flet/tests/test_navigation_rail.py deleted file mode 100644 index 2b4774580..000000000 --- a/sdk/python/packages/flet/tests/test_navigation_rail.py +++ /dev/null @@ -1,31 +0,0 @@ -import flet as ft -from flet.core.protocol import Command - - -def test_instance_no_attrs_set(): - r = ft.NavigationRail() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["navigationrail"], - attrs={"rtl": "false"}, - commands=[], - ) - ], "Test failed" - - -def test_extension_set_enum(): - r = ft.NavigationRail() - assert r.label_type is None - assert r._get_attr("labelType") is None - - r = ft.NavigationRail(label_type=ft.NavigationRailLabelType.SELECTED) - assert isinstance(r.label_type, ft.NavigationRailLabelType) - assert r.label_type == ft.NavigationRailLabelType.SELECTED - assert r._get_attr("labelType") == "selected" - - r = ft.NavigationRail(label_type="none") - assert isinstance(r.label_type, str) - assert r._get_attr("labelType") == "none" diff --git a/sdk/python/packages/flet/tests/test_object_diff_frozen.py b/sdk/python/packages/flet/tests/test_object_diff_frozen.py new file mode 100644 index 000000000..f21928f5f --- /dev/null +++ b/sdk/python/packages/flet/tests/test_object_diff_frozen.py @@ -0,0 +1,813 @@ +from dataclasses import dataclass +from typing import Optional + +import flet as ft +import pytest +from flet.controls.base_control import BaseControl, control + +from .common import ( + LineChart, + LineChartData, + LineChartDataPoint, + cmp_ops, + make_diff, + make_msg, +) + + +def test_compare_roots(): + c1 = {} + c2 = ft.Column() + c2._frozen = True + patch, _, _, _ = make_diff(c2, c1) + assert len(patch) == 1 + assert cmp_ops(patch, [{"op": "replace", "path": [], "value_type": ft.Column}]) + + +def test_compare_literals_removed(): + c1 = ft.Column([1, 2, 3, 4, 5, 6, 7, 8]) + c2 = ft.Column([1, 4, 5, 5]) + c1._frozen = True + patch, _, _, _ = make_diff(c2, c1) + assert cmp_ops( + patch, + [ + {"op": "remove", "path": ["controls", 1], "value": 2}, + {"op": "replace", "path": ["controls", 1], "value": 5}, + {"op": "move", "from": ["controls", 2], "path": ["controls", 1]}, + {"op": "remove", "path": ["controls", 4], "value": 6}, + {"op": "remove", "path": ["controls", 4], "value": 7}, + {"op": "remove", "path": ["controls", 4], "value": 8}, + ], + ) + + +def test_compare_literals_added(): + c1 = ft.Column([1, 2, 3, 4, 5]) + c2 = ft.Column([1, 4, 5, 6, 7, 8, 9]) + c1._frozen = True + patch, _, _, _ = make_diff(c2, c1) + assert cmp_ops( + patch, + [ + {"op": "remove", "path": ["controls", 1], "value": 2}, + {"op": "remove", "path": ["controls", 1], "value": 3}, + {"op": "move", "from": ["controls", 1], "path": ["controls", 1]}, + {"op": "add", "path": ["controls", 2], "value": 6}, + {"op": "move", "from": ["controls", 3], "path": ["controls", 2]}, + {"op": "add", "path": ["controls", 4], "value": 7}, + {"op": "add", "path": ["controls", 5], "value": 8}, + {"op": "add", "path": ["controls", 6], "value": 9}, + ], + ) + + +def test_compare_literals_replaced(): + c1 = ft.Column([1, 2]) + c2 = ft.Column([3, 4, 5]) + c1._frozen = True + patch, _, _, _ = make_diff(c2, c1) + assert cmp_ops( + patch, + [ + {"op": "replace", "path": ["controls", 0], "value": 3}, + {"op": "replace", "path": ["controls", 1], "value": 4}, + {"op": "add", "path": ["controls", 2], "value": 5}, + ], + ) + + +def test_compare_objects_replaced_no_keys(): + @dataclass + class Item: + x: int + y: int + + c1 = ft.Column( + [ + Item(3, 1), + Item(4, 1), + ], + ) + c2 = ft.Column( + [ + Item(1, 0), + Item(2, 0), + Item(3, 0), + ] + ) + c1._frozen = True + patch, _, _, _ = make_diff(c2, c1) + assert cmp_ops( + patch, + [ + {"op": "replace", "path": ["controls", 0, "x"], "value": 1}, + {"op": "replace", "path": ["controls", 0, "y"], "value": 0}, + {"op": "replace", "path": ["controls", 1, "x"], "value": 2}, + {"op": "replace", "path": ["controls", 1, "y"], "value": 0}, + {"op": "add", "path": ["controls", 2], "value_type": Item}, + ], + ) + + +def test_compare_objects_replaced_with_control_keys(): + @dataclass + class Item: + key: int + y: int + + c1 = ft.Column( + [ + Item(3, 1), + Item(4, 1), + ], + ) + c2 = ft.Column( + [ + Item(1, 0), + Item(2, 0), + ] + ) + c1._frozen = True + patch, _, _, _ = make_diff(c2, c1) + assert cmp_ops( + patch, + [ + {"op": "replace", "path": ["controls", 0], "value_type": Item}, + {"op": "replace", "path": ["controls", 1], "value_type": Item}, + ], + ) + assert patch[0]["value"].key == 1 + assert patch[0]["value"].y == 0 + assert patch[1]["value"].key == 2 + assert patch[1]["value"].y == 0 + + +def test_compare_objects_updated_and_moved_with_control_keys(): + @control("Item") + class Item(BaseControl): + y: int + + c1 = ft.Column( + [ + Item(key=2, y=0), + Item(key=1, y=0), + ], + ) + c2 = ft.Column( + [ + Item(key=1, y=1), + Item(key=2, y=2), + ] + ) + c1._frozen = True + patch, _, _, _ = make_diff(c2, c1) + assert cmp_ops( + patch, + [ + {"op": "replace", "path": ["controls", 0, "y"], "value": 1}, + {"op": "replace", "path": ["controls", 1, "y"], "value": 2}, + {"op": "move", "from": ["controls", 0], "path": ["controls", 1]}, + ], + ) + + +def test_compare_objects_added(): + @control("Item") + class Item(BaseControl): + y: int + + c1 = ft.Column( + [ + Item(key=3, y=1), + Item(key=4, y=1), + Item(key=5, y=1), + Item(key=6, y=1), + ], + ) + c2 = ft.Column( + [ + Item(key=1, y=0), + Item(key=2, y=0), + Item(key=4, y=0), + Item(key=3, y=0), + Item(key=5, y=0), + Item(key=6, y=0), + Item(key=7, y=0), + Item(key=8, y=0), + ] + ) + c1._frozen = True + patch, _, _, _ = make_diff(c2, c1) + assert cmp_ops( + patch, + [ + {"op": "add", "path": ["controls", 0], "value_type": Item}, + {"op": "add", "path": ["controls", 1], "value_type": Item}, + {"op": "replace", "path": ["controls", 2, "y"], "value": 0}, + {"op": "replace", "path": ["controls", 3, "y"], "value": 0}, + {"op": "move", "from": ["controls", 2], "path": ["controls", 3]}, + {"op": "replace", "path": ["controls", 4, "y"], "value": 0}, + {"op": "replace", "path": ["controls", 5, "y"], "value": 0}, + {"op": "add", "path": ["controls", 6], "value_type": Item}, + {"op": "add", "path": ["controls", 7], "value_type": Item}, + ], + ) + + +def test_compare_controls(): + def on_scroll(e): + pass + + r1 = ft.Row( + controls=[ft.Text("Hello"), ft.Text("World")], + spacing=10, + scale=ft.Scale(0.5, scale_x=0.1, scale_y=0.2), + vertical_alignment=ft.CrossAxisAlignment.CENTER, + on_scroll=on_scroll, + ) + r2 = ft.Row( + controls=[ft.Text("Hello"), ft.Text("World")], + spacing=10, + scale=ft.Scale(0.5, scale_x=0.1, scale_y=0.2), + vertical_alignment=ft.CrossAxisAlignment.CENTER, + on_scroll=on_scroll, + ) + assert r1 == r2 + + dp1 = LineChartDataPoint(x=10, y=20) + dp2 = LineChartDataPoint(x=10, y=20) + assert dp1 == dp2 + + dp3 = dp2 + assert dp2 is dp3 + + r3 = ft.Row(spacing=20) + r4 = ft.Row(spacing=10) + assert r3 != r4 + + +def test_button_basic_diff(): + b1 = ft.Button(content="Hello") + b2 = ft.Button( + content="Click me", + style=ft.ButtonStyle(color=ft.Colors.RED), + scale=ft.Scale(0.2), + ) + b1._frozen = True + b2._frozen = True + + # initial iteration + patch, _, _, _ = make_diff(b2, {}) + assert not hasattr(b2, "__prev_classes") + assert isinstance(patch[0]["value"], ft.Button) + + # 2nd iteration + patch, _, _, _ = make_diff(b2, b1) + assert len(patch) == 3 + assert cmp_ops( + patch, + [ + {"op": "replace", "path": ["scale"], "value_type": ft.Scale}, + {"op": "replace", "path": ["content"], "value": "Click me"}, + {"op": "replace", "path": ["style"], "value_type": ft.ButtonStyle}, + ], + ) + + # 3rd iteration + b3 = ft.Button(content=ft.Text("Text_1"), style=None, scale=ft.Scale(0.1)) + b3._frozen = True + patch, _, _, _ = make_diff(b3, b2) + assert cmp_ops( + patch, + [ + {"op": "replace", "path": ["scale", "scale"], "value": 0.1}, + {"op": "replace", "path": ["content"], "value_type": ft.Text}, + {"op": "replace", "path": ["style"], "value": None}, + ], + ) + + +def test_lists_with_key_diff(): + c1 = LineChart( + data_series=[ + LineChartData( + points=[ + LineChartDataPoint(key=0, x=0, y=1), + LineChartDataPoint(key=1, x=1, y=2), + LineChartDataPoint(key=2, x=2, y=3), + ] + ) + ] + ) + c2 = LineChart( + data_series=[ + LineChartData( + points=[ + LineChartDataPoint(key=1, x=1, y=2), + LineChartDataPoint(key=2, x=2, y=2), + LineChartDataPoint(key=3, x=3, y=5), + ] + ) + ] + ) + c1._frozen = True + patch, _, _, _ = make_diff(c2, c1) + assert c2._frozen + assert c2.data_series[0]._frozen + assert cmp_ops( + patch, + [ + {"op": "remove", "path": ["data_series", 0, "points", 0]}, + { + "op": "replace", + "path": ["data_series", 0, "points", 1, "y"], + "value": 2, + }, + { + "op": "add", + "path": ["data_series", 0, "points", 2], + "value_type": LineChartDataPoint, + }, + {"op": "replace", "path": ["_skip_inherited_notifier"], "value": True}, + ], + ) + assert patch[2]["value"].x == 3 + assert patch[2]["value"].y == 5 + + +def test_lists_with_no_key_diff(): + c1 = LineChart( + data_series=[ + LineChartData( + points=[ + LineChartDataPoint(x=0, y=1), + LineChartDataPoint(x=1, y=2), + LineChartDataPoint(x=2, y=3), + ] + ) + ] + ) + c2 = LineChart( + data_series=[ + LineChartData( + points=[ + LineChartDataPoint(x=1, y=2), + LineChartDataPoint(x=2, y=2), + LineChartDataPoint(x=3, y=5), + ] + ) + ] + ) + c1._frozen = True + patch, _, _, _ = make_diff(c2, c1) + assert c2._frozen + assert c2.data_series[0]._frozen + assert cmp_ops( + patch, + [ + { + "op": "replace", + "path": ["data_series", 0, "points", 0, "x"], + "value": 1, + }, + { + "op": "replace", + "path": ["data_series", 0, "points", 0, "y"], + "value": 2, + }, + { + "op": "replace", + "path": ["data_series", 0, "points", 1, "x"], + "value": 2, + }, + { + "op": "replace", + "path": ["data_series", 0, "points", 2, "x"], + "value": 3, + }, + { + "op": "replace", + "path": ["data_series", 0, "points", 2, "y"], + "value": 5, + }, + {"op": "replace", "path": ["_skip_inherited_notifier"], "value": True}, + ], + ) + + +def test_simple_lists_diff_1(): + c1 = LineChart(data_series=[LineChartData(points=[1, 2, 3])]) + c2 = LineChart(data_series=[LineChartData(points=[2, 3, 4])]) + c1._frozen = True + patch, _, _, _ = make_diff(c2, c1) + assert cmp_ops( + patch, + [ + {"op": "remove", "path": ["data_series", 0, "points", 0], "value": 1}, + {"op": "add", "path": ["data_series", 0, "points", 2], "value": 4}, + {"op": "replace", "path": ["_skip_inherited_notifier"], "value": True}, + ], + ) + + +def test_simple_lists_diff_2(): + c1 = LineChart(data_series=[LineChartData(points=[1, 2, 3, 4])]) + c2 = LineChart(data_series=[LineChartData(points=[1, 3, 4])]) + c1._frozen = True + patch, _, _, _ = make_diff(c2, c1) + assert cmp_ops( + patch, + [ + {"op": "remove", "path": ["data_series", 0, "points", 1], "value": 2}, + {"op": "replace", "path": ["_skip_inherited_notifier"], "value": True}, + ], + ) + + +def test_similar_lists_diff(): + c1 = LineChart(data_series=[LineChartData(points=[ft.Scale(0), ft.Scale(1)])]) + c2 = LineChart(data_series=[LineChartData(points=[ft.Scale(1), ft.Scale(2)])]) + c1._frozen = True + patch, _, _, _ = make_diff(c2, c1) + assert cmp_ops( + patch, + [ + { + "op": "replace", + "path": ["data_series", 0, "points", 0, "scale"], + "value": 1, + }, + { + "op": "replace", + "path": ["data_series", 0, "points", 1, "scale"], + "value": 2, + }, + {"op": "replace", "path": ["_skip_inherited_notifier"], "value": True}, + ], + ) + + +def test_lists_in_place(): + c1 = LineChart( + data_series=[ + LineChartData( + points=[ + LineChartDataPoint(x=0, y=1), + LineChartDataPoint(x=1, y=2), + LineChartDataPoint(x=2, y=3), + ] + ) + ] + ) + _, patch, _, _, _ = make_msg(c1, {}) + + # 1st change + c1.data_series[0].points.pop(0) + c1.data_series[0].points[1].y = 10 + c1.data_series[0].points.append(LineChartDataPoint(x=3, y=4)) + c1.data_series.append( + LineChartData( + points=[ + LineChartDataPoint(x=10, y=20), + ] + ) + ) + patch, msg, added_controls, removed_controls = make_diff(c1) + assert cmp_ops( + patch, + [ + {"op": "remove", "path": ["data_series", 0, "points", 0]}, + { + "op": "replace", + "path": ["data_series", 0, "points", 1, "y"], + "value": 10, + }, + { + "op": "add", + "path": ["data_series", 0, "points", 2], + "value_type": LineChartDataPoint, + }, + {"op": "add", "path": ["data_series", 1], "value_type": LineChartData}, + ], + ) + + +def test_both_frozen_hosted_by_in_place(): + def chart(data): + r = LineChart( + data_series=[ + LineChartData( + points=[ + LineChartDataPoint(key=dp[0], x=dp[0], y=dp[1]) for dp in ds + ] + ) + for ds in data + ] + ) + r._frozen = True + return r + + c = ft.Container(content=chart([[(0, 1), (1, 1), (2, 2)], [(10, 20), (20, 30)]])) + assert not hasattr(c, "_frozen") + _, patch, _, added_controls, removed_controls = make_msg(c, {}) + assert len(added_controls) == 9 + assert len(removed_controls) == 0 + assert hasattr(c, "__changes") + assert not hasattr(c, "_frozen") + + c.alignment = ft.Alignment.bottom_center() + c.bgcolor = ft.Colors.AMBER + ch = chart([[(1, 1), (2, 2), (3, 3)]]) + c.content = ch + patch, _, added_controls, removed_controls = make_diff(c, c) + # for ac in added_controls: + # print("\nADDED CONTROL:", ac) + # for rc in removed_controls: + # print("\nREMOVED CONTROL:", rc) + # 5 x Added controls: LineChart + LineChartData + + # 3 x LineChartDataPoint (2 moved and 1 new) + assert len(added_controls) == 5 + # 3 x Removed controls: LineChart + 2 x LineChartData + 5 x LineChartDataPoint + assert len(removed_controls) == 8 + assert cmp_ops( + patch, + [ + {"op": "replace", "path": ["alignment"], "value": ft.Alignment(x=0, y=1)}, + {"op": "replace", "path": ["bgcolor"], "value": ft.Colors.AMBER}, + {"op": "remove", "path": ["content", "data_series", 0, "points", 0]}, + { + "op": "add", + "path": ["content", "data_series", 0, "points", 2], + "value_type": LineChartDataPoint, + }, + { + "op": "remove", + "path": ["content", "data_series", 1], + "value_type": LineChartData, + }, + ], + ) + assert hasattr(ch, "_frozen") + with pytest.raises(Exception, match="Controls inside data view cannot be updated."): + ch.width = 100 + + +def test_larger_control_updates(): + c1 = ft.Container( + content=ft.Row([ft.Text("Text 1")]), + bgcolor=ft.Colors.YELLOW, + width=200, + height=100, + scale=ft.Scale(1.0), + ) + c1._frozen = True + c2 = ft.Container( + content=ft.Row([ft.Text("Text 2")]), + bgcolor=ft.Colors.RED, + width=200, + scale=ft.Scale(2.0), + ) + c2._frozen = True + patch, msg, added_controls, removed_controls = make_diff(c2, c1) + assert cmp_ops( + patch, + [ + {"op": "replace", "path": ["height"], "value": None}, + {"op": "replace", "path": ["scale", "scale"], "value": 2.0}, + { + "op": "replace", + "path": ["content", "controls", 0, "value"], + "value": "Text 2", + }, + {"op": "replace", "path": ["bgcolor"], "value": ft.Colors.RED}, + ], + ) + + +@dataclass +class User: + id: int + name: str + age: int + verified: bool + + +users = [ + User(1, "John Smith", 20, True), + User(2, "Alice Wong", 32, True), + User(3, "Bob Bar", 40, False), +] + + +def test_control_builder(): + @dataclass + class State: + msg: str + + state = State(msg="some text") + + dv = ft.ControlBuilder(state, builder=lambda state: ft.Text(state.msg)) + _, patch, _, added_controls, removed_controls = make_msg(dv, {}) + assert len(added_controls) == 2 + assert len(removed_controls) == 0 + assert cmp_ops( + patch, + [{"op": "replace", "path": [], "value_type": ft.ControlBuilder}], + ) + assert isinstance(patch[0]["value"].content, ft.Text) + assert hasattr(patch[0]["value"].content, "_frozen") + assert patch[0]["value"].content.value == "some text" + + state.msg = "Hello, world!" + patch, msg, added_controls, removed_controls = make_diff(dv, dv) + assert len(patch) == 1 + assert cmp_ops( + patch, + [{"op": "replace", "path": ["content", "value"], "value": "Hello, world!"}], + ) + + +def test_nested_control_builders(): + @dataclass + class AppState: + count: int + + state = AppState(count=0) + + cb = ft.ControlBuilder( + state, + lambda state: ft.SafeArea( + ft.Container( + ft.ControlBuilder( + state, + lambda state: ft.Text( + value=f"{state.count}", + spans=[ + ft.TextSpan( + f"SPAN {state.count}", + on_click=lambda: print("span clicked!"), + ) + ] + if state.count > 0 + else [], + size=50, + ), + ), + alignment=ft.Alignment.center(), + ), + expand=True, + ), + expand=True, + ) + _, patch, _, added_controls, removed_controls = make_msg(cb, {}) + assert len(added_controls) == 5 + assert len(removed_controls) == 0 + assert not hasattr(patch[0]["value"], "_frozen") # ControlBuilder + assert hasattr(patch[0]["value"].content, "_frozen") # SafeArea + assert hasattr(patch[0]["value"].content.content, "_frozen") # Center + assert hasattr( + patch[0]["value"].content.content.content, "_frozen" + ) # ControlBuilder (nested) + assert hasattr(patch[0]["value"].content.content.content.content, "_frozen") # Text + + state.count = 10 + patch, msg, added_controls, removed_controls = make_diff(cb, cb) + assert len(added_controls) == 5 + assert len(removed_controls) == 4 + assert cmp_ops( + patch, + [ + { + "op": "replace", + "path": ["content", "content", "content", "content", "value"], + "value": "10", + }, + { + "op": "replace", + "path": ["content", "content", "content", "content", "spans"], + "value_type": list, + }, + ], + ) + assert isinstance(patch[1]["value"][0], ft.TextSpan) + assert patch[1]["value"][0].text == "SPAN 10" + assert hasattr(patch[1]["value"][0], "_frozen") # TextSpan + + state.count = 0 + patch, msg, added_controls, removed_controls = make_diff(cb, cb) + assert len(added_controls) == 4 + assert len(removed_controls) == 5 + assert cmp_ops( + patch, + [ + { + "op": "replace", + "path": ["content", "content", "content", "content", "value"], + "value": "0", + }, + { + "op": "replace", + "path": ["content", "content", "content", "content", "spans"], + "value": [], + }, + ], + ) + + +def test_data_view_with_cache(): + @ft.data_view + def user_details(user: User): + return ft.Card( + ft.Column( + [ + ft.Text(f"Name: {user.name}"), + ft.Text(f"Age: {user.age}"), + ft.Checkbox(label="Verified", value=user.verified), + ] + ), + key=user.id, + ) + + @ft.data_view + def users_list(users): + return ft.Column([user_details(user) for user in users]) + + page = ft.Row([users_list(users)]) + + _, patch, _, added_controls, removed_controls = make_msg(page, {}) + assert len(added_controls) == 17 + assert len(removed_controls) == 0 + + # add new user + users.append(User(4, name="Someone Else", age=99, verified=False)) + page.controls[0] = users_list(users) + patch, msg, added_controls, removed_controls = make_diff(page, page) + assert len(added_controls) == 6 + assert len(removed_controls) == 1 + assert cmp_ops( + patch, + [{"op": "add", "path": ["controls", 0, "controls", 3], "value_type": ft.Card}], + ) + + # Card ids: 9, 14, 19, 26 + + # remove user + del users[1] + page.controls[0] = users_list(users) + + # OLD: 9, 14, 19, 26 + # NEW: 9, 19, 26 + + patch, msg, added_controls, removed_controls = make_diff(page, page) + assert cmp_ops( + patch, + [{"op": "remove", "path": ["controls", 0, "controls", 1]}], + ) + # for ac in added_controls: + # print("\nADDED CONTROL:", ac) + # for rc in removed_controls: + # print("\nREMOVED CONTROL:", rc) + assert len(added_controls) == 1 + assert len(removed_controls) == 6 + + +def test_empty_data_view(): + @ft.data_view + def my_view(): + return None + + v = my_view() + assert v is None + + +def test_login_logout_view(): + class AppState: + logged_username: Optional[str] = None + + def login(self, _): + self.logged_username = "John" + + def logout(self, _): + self.logged_username = None + + state = AppState() + + @ft.data_view + def login_view(state: AppState): + return ( + ft.Column( + [ + ft.Text(state.logged_username), + ft.Button("Logout", on_click=state.logout), + ] + ) + if state.logged_username + else ft.Column( + [ + ft.Button("Login", on_click=state.login), + ] + ) + ) + + app = ft.View("/", [login_view(state)]) diff --git a/sdk/python/packages/flet/tests/test_object_diff_in_place.py b/sdk/python/packages/flet/tests/test_object_diff_in_place.py new file mode 100644 index 000000000..8a8042762 --- /dev/null +++ b/sdk/python/packages/flet/tests/test_object_diff_in_place.py @@ -0,0 +1,510 @@ +from dataclasses import field +from typing import Any, Optional + +import flet as ft +from flet.controls.base_control import control +from flet.controls.buttons import ButtonStyle +from flet.controls.colors import Colors +from flet.controls.control import Control +from flet.controls.core.gesture_detector import GestureDetector +from flet.controls.core.text import Text +from flet.controls.events import DragUpdateEvent +from flet.controls.material.button import Button + +# import flet as ft +# import flet.canvas as cv +from flet.controls.material.container import Container +from flet.controls.material.elevated_button import ElevatedButton +from flet.controls.page import Page +from flet.controls.painting import Paint, PaintLinearGradient +from flet.controls.ref import Ref +from flet.controls.services.service import Service +from flet.messaging.connection import Connection +from flet.messaging.session import Session +from flet.pubsub.pubsub_hub import PubSubHub + +from .common import ( + LineChart, + LineChartData, + LineChartDataPoint, + b_unpack, + cmp_ops, + make_diff, + make_msg, +) + + +@control +class SuperElevatedButton(ElevatedButton): + prop_2: Optional[str] = None + + def init(self): + print("SuperElevatedButton.init()") + assert self.page + + +@control("MyButton") +class MyButton(ElevatedButton): + prop_1: Optional[str] = None + + +@control("MyService") +class MyService(Service): + prop_1: Optional[str] = None + prop_2: list[int] = field(default_factory=list) + + +@control("Span") +class Span(Control): + text: Optional[str] = None + cls: Optional[str] = None + controls: list[Any] = field(default_factory=list) + head_controls: list[Any] = field(default_factory=list) + + +@control("Div") +class Div(Control): + cls: Optional[str] = None + some_value: Any = None + controls: list[Any] = field(default_factory=list) + + +def test_control_type(): + btn = ElevatedButton("some button") + assert btn._c == "ElevatedButton" + + +def test_control_id(): + btn = ElevatedButton("some button") + assert btn._i > 0 + + +def test_inherited_control_has_the_same_type(): + btn = SuperElevatedButton(prop_2="2") + assert btn._c == "ElevatedButton" + + +def test_inherited_control_with_overridden_type(): + btn = MyButton(prop_1="1") + assert btn._c == "MyButton" + + +def test_control_ref(): + page_ref = Ref[Page]() + conn = Connection() + conn.pubsubhub = PubSubHub() + page = Page(sess=Session(conn), ref=page_ref) + + assert page_ref.current == page + + +def test_simple_page(): + conn = Connection() + conn.pubsubhub = PubSubHub() + page = Page(sess=Session(conn)) + page.controls = [Div(cls="div_1", some_value="Text")] + page.data = 100000 + page.bgcolor = Colors.GREEN + page.fonts = {"font1": "font_url_1", "font2": "font_url_2"} + page.on_login = lambda e: print("on login") + page.services.append(MyService(prop_1="Hello", prop_2=[1, 2, 3])) + + # page and window have hard-coded IDs + assert page._i == 1 + assert page.window and page.window._i == 2 + + msg, _, _, added_controls, removed_controls = make_msg(page, {}, show_details=True) + u_msg = b_unpack(msg) + assert len(added_controls) == 19 + assert len(removed_controls) == 0 + + assert page.parent is None + assert page.controls[0].parent == page.views[0] + assert page.clipboard + assert page.clipboard.parent + assert page.clipboard.page + + print(u_msg) + + assert isinstance(u_msg, list) + assert u_msg[0] == [0] + assert len(u_msg[1]) == 4 + p = u_msg[1][3] + assert p["_i"] > 0 + assert p["on_login"] + assert len(p["views"]) > 0 + assert "on_connect" not in p + # assert u_msg == [ + # [0], + # [ + # 0, + # 0, + # 0, + # { + # "_i": 1, + # "_c": "Page", + # "views": [ + # { + # "_i": 17, + # "_c": "View", + # "controls": [ + # { + # "_i": 29, + # "_c": "Div", + # "cls": "div_1", + # "some_value": "Text", + # } + # ], + # "bgcolor": "green", + # } + # ], + # "_overlay": {"_i": 18, "_c": "Overlay"}, + # "_dialogs": {"_i": 19, "_c": "Dialogs"}, + # "window": {"_i": 2, "_c": "Window"}, + # "browser_context_menu": {"_i": 21, "_c": "BrowserContextMenu"}, + # "shared_preferences": {"_i": 22, "_c": "SharedPreferences"}, + # "clipboard": {"_i": 23, "_c": "Clipboard"}, + # "storage_paths": {"_i": 24, "_c": "StoragePaths"}, + # "url_launcher": {"_i": 25, "_c": "UrlLauncher"}, + # "_user_services": { + # "_i": 26, + # "_c": "ServiceRegistry", + # "services": [ + # { + # "_i": 30, + # "_c": "MyService", + # "prop_1": "Hello", + # "prop_2": [1, 2, 3], + # } + # ], + # }, + # "_page_services": { + # "_i": 27, + # "_c": "ServiceRegistry", + # "services": [ + # {"_i": 21, "_c": "BrowserContextMenu"}, + # {"_i": 22, "_c": "SharedPreferences"}, + # {"_i": 23, "_c": "Clipboard"}, + # {"_i": 25, "_c": "UrlLauncher"}, + # {"_i": 24, "_c": "StoragePaths"}, + # ], + # }, + # "fonts": {"font1": "font_url_1", "font2": "font_url_2"}, + # "on_login": True, + # }, + # ], + # ] + + # update sub-tree + page.on_login = None + page.controls[0].some_value = "Another text" + page.controls[0].controls = [ + SuperElevatedButton( + "Button 😬", + style=ButtonStyle(color=Colors.RED), + on_click=lambda e: print(e), + opacity=1, + ref=None, + ), + SuperElevatedButton("Another Button"), + ] + del page.fonts["font2"] + assert page.controls[0].controls[0].page is None + + page.services[0].prop_2 = [2, 6] + + # add 2 new buttons to a list + _, patch, _, added_controls, removed_controls = make_msg(page, show_details=True) + assert hasattr(page.views[0], "__changes") + assert len(added_controls) == 2 + assert len(removed_controls) == 0 + assert len(patch) == 7 + assert cmp_ops( + patch, + [ + {"op": "replace", "path": ["on_login"], "value": False}, + { + "op": "replace", + "path": ["views", 0, "controls", 0, "some_value"], + "value": "Another text", + }, + { + "op": "replace", + "path": ["views", 0, "controls", 0, "controls"], + # "value": [SuperElevatedButton, SuperElevatedButton], + }, + {"op": "remove", "path": ["fonts", "font2"], "value": "font_url_2"}, + { + "op": "remove", + "path": ["_user_services", "services", 0, "prop_2", 0], + "value": 1, + }, + { + "op": "add", + "path": ["_user_services", "services", 0, "prop_2", 1], + "value": 6, + }, + { + "op": "remove", + "path": ["_user_services", "services", 0, "prop_2", 2], + "value": 3, + }, + ], + ) + assert len(patch[2]["value"]) == 2 + assert isinstance(patch[2]["value"][0], SuperElevatedButton) + assert isinstance(patch[2]["value"][1], SuperElevatedButton) + + # replace control in a list + page.controls[0].controls[0] = SuperElevatedButton("Foo") + _, patch, _, added_controls, removed_controls = make_msg(page, show_details=True) + # for ac in added_controls: + # print("\nADDED CONTROL:", ac) + # for rc in removed_controls: + # print("\nREMOVED CONTROL:", rc) + assert len(added_controls) == 1 + assert len(removed_controls) == 1 + assert cmp_ops( + patch, + [ + { + "op": "replace", + "path": ["views", 0, "controls", 0, "controls", 0], + "value_type": SuperElevatedButton, + } + ], + ) + + # insert a new button to the start of a list + page.controls[0].controls.insert(0, SuperElevatedButton("Bar")) + page.controls[0].controls[1].content = "Baz" + _, patch, _, added_controls, removed_controls = make_msg(page, show_details=True) + assert len(added_controls) == 1 + assert len(removed_controls) == 0 + assert cmp_ops( + patch, + [ + { + "op": "add", + "path": ["views", 0, "controls", 0, "controls", 0], + "value_type": SuperElevatedButton, + }, + { + "op": "replace", + "path": ["views", 0, "controls", 0, "controls", 1, "content"], + "value": "Baz", + }, + ], + ) + + page.controls[0].controls.clear() + _, patch, _, added_controls, removed_controls = make_msg(page, show_details=True) + assert len(added_controls) == 0 + assert len(removed_controls) == 3 + assert cmp_ops( + patch, + [ + { + "op": "replace", + "path": ["views", 0, "controls", 0, "controls"], + "value": [], + } + ], + ) + + +def test_floating_action_button(): + conn = Connection() + conn.pubsubhub = PubSubHub() + page = Page(sess=Session(conn)) + + # initial update + make_msg(page, {}, show_details=True) + + # second update + 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.controls.append( + ft.SafeArea( + ft.Container( + counter, + alignment=ft.Alignment.center(), + bgcolor=ft.Colors.YELLOW, + expand=True, + ), + expand=True, + ), + ) + + patch, _, added_controls, removed_controls = make_diff(page, show_details=True) + assert cmp_ops( + patch, + [ + { + "op": "replace", + "path": ["views", 0, "floating_action_button"], + "value_type": ft.FloatingActionButton, + }, + {"op": "replace", "path": ["views", 0, "controls"]}, + ], + ) + assert len(patch[1]["value"]) == 1 + assert isinstance(patch[1]["value"][0], ft.SafeArea) + + +def test_changes_tracking(): + conn = Connection() + conn.pubsubhub = PubSubHub() + page = Page(sess=Session(conn)) + page.controls.append( + btn := Button(Text("Click me!"), on_click=lambda e: print("clicked!")) + ) + + # initial update + make_msg(page, {}, show_details=True) + + # second update + btn.content = Text("A new button content") + btn.width = 300 + btn.height = 100 + + # t1 = Text("AAA") + # t2 = Text("BBB") + page.controls.append(Text("Line 2")) + + patch, _, added_controls, removed_controls = make_diff(page, show_details=True) + assert cmp_ops( + patch, + [ + { + "op": "replace", + "path": ["views", 0, "controls", 0, "content"], + "value_type": ft.Text, + }, + { + "op": "replace", + "path": ["views", 0, "controls", 0, "width"], + "value": 300, + }, + { + "op": "replace", + "path": ["views", 0, "controls", 0, "height"], + "value": 100, + }, + {"op": "add", "path": ["views", 0, "controls", 1], "value_type": ft.Text}, + ], + ) + + +def test_large_updates(): + import flet.canvas as cv + + conn = Connection() + conn.pubsubhub = PubSubHub() + page = Page(sess=Session(conn)) + + def pan_update(e: DragUpdateEvent): + pass + + page.controls.append( + Container( + cp := cv.Canvas( + [ + cv.Fill( + Paint( + gradient=PaintLinearGradient( + (0, 0), (600, 600), colors=[Colors.CYAN_50, Colors.GREY] + ) + ) + ), + ], + content=GestureDetector( + on_pan_update=pan_update, + drag_interval=30, + ), + expand=False, + ), + border_radius=5, + width=float("inf"), + expand=True, + ) + ) + + # initial update + _, patch, _, added_controls, removed_controls = make_msg( + page, {}, show_details=True + ) + + # second update + for i in range(1, 1000): + cp.shapes.append( + cv.Line(i + 1, i + 100, i + 10, i + 20, paint=Paint(stroke_width=3)) + ) + + make_msg(cp, show_details=False) + + cp.shapes[100].x1 = 12 + + # third update + for i in range(1, 20): + cp.shapes.append( + cv.Line(i + 1, i + 100, i + 10, i + 20, paint=Paint(stroke_width=3)) + ) + + _, patch, _, added_controls, removed_controls = make_msg(cp, show_details=True) + + +def test_add_remove_lists(): + data = [[(0, 1), (1, 2), (2, 3)]] + chart = LineChart( + data_series=[ + LineChartData( + points=[LineChartDataPoint(key=dp[0], x=dp[0], y=dp[1]) for dp in ds] + ) + for ds in data + ] + ) + _, patch, _, _, _ = make_msg(chart, {}) + + # add/remove + chart.data_series[0].points.pop(0) + chart.data_series[0].points.append(LineChartDataPoint(x=3, y=4)) + + patch, _, _, _ = make_diff(chart, chart) + assert cmp_ops( + patch, + [ + {"op": "remove", "path": ["data_series", 0, "points", 0]}, + { + "op": "add", + "path": ["data_series", 0, "points", 2], + "value_type": LineChartDataPoint, + }, + ], + ) + + +def test_reverse_list(): + col = ft.Column([ft.Text("Line 1"), ft.Text("Line 2"), ft.Text("Line 3")]) + _, patch, _, _, _ = make_msg(col, {}) + + # reverse + col.controls.reverse() + patch, _, _, _ = make_diff(col) + assert col.controls[0].value == "Line 3" + assert col.controls[2].value == "Line 1" + assert cmp_ops( + patch, + [ + {"op": "move", "from": ["controls", 2], "path": ["controls", 0]}, + {"op": "move", "from": ["controls", 1], "path": ["controls", 2]}, + ], + ) diff --git a/sdk/python/packages/flet/tests/test_page.py b/sdk/python/packages/flet/tests/test_page.py deleted file mode 100644 index 52e9adca9..000000000 --- a/sdk/python/packages/flet/tests/test_page.py +++ /dev/null @@ -1,6 +0,0 @@ -import pytest - - -@pytest.mark.skip(reason="no way of currently testing this") -def test_page(page): - assert page.url != "" and page.url.startswith("http"), "Test failed" diff --git a/sdk/python/packages/flet/tests/test_patch_dataclass.py b/sdk/python/packages/flet/tests/test_patch_dataclass.py new file mode 100644 index 000000000..fa4367552 --- /dev/null +++ b/sdk/python/packages/flet/tests/test_patch_dataclass.py @@ -0,0 +1,141 @@ +from dataclasses import dataclass + +import msgpack +from flet.controls.base_control import BaseControl +from flet.controls.object_patch import ObjectPatch +from flet.controls.padding import Padding +from flet.controls.page import Page +from flet.controls.page_view import PageMediaData +from flet.controls.types import Brightness, PagePlatform +from flet.messaging.connection import Connection +from flet.messaging.protocol import configure_encode_object_for_msgpack +from flet.messaging.session import Session +from flet.pubsub.pubsub_hub import PubSubHub +from flet.utils import patch_dataclass + + +def test_simple_patch_dataclass(): + @dataclass + class Config: + retries: int + timeout: float + + @dataclass + class AppSettings: + debug: bool + config: Config + + settings = AppSettings(debug=False, config=Config(retries=3, timeout=1.0)) + + patch = {"debug": True, "config": {"timeout": 2.5}} + + patch_dataclass(settings, patch) + + assert settings.debug + assert isinstance(settings.config, Config) + assert settings.config.timeout == 2.5 + + +def test_page_patch_dataclass(): + conn = Connection() + conn.pubsubhub = PubSubHub() + page = Page(sess=Session(conn)) + + assert page.window.width is None + assert page.window.height is None + assert page.debug is False + + patch_dataclass( + page, + { + "pwa": False, + "web": False, + "debug": True, + "window": { + "maximized": False, + "minimized": False, + "full_screen": False, + "always_on_top": False, + "focused": True, + "visible": True, + "width": 800.0, + "height": 628.0, + "top": 232.0, + "left": -1360.0, + "opacity": 1.0, + }, + "platform_brightness": "light", + "media": { + "padding": {"top": 0.0, "right": 0.0, "bottom": 0.0, "left": 0.0}, + "view_padding": {"top": 0.0, "right": 0.0, "bottom": 0.0, "left": 0.0}, + "view_insets": {"top": 0.0, "right": 0.0, "bottom": 0.0, "left": 0.0}, + }, + "width": 800.0, + "height": 600.0, + "route": "/", + "platform": "macos", + }, + ) + + # 1 -calculate diff + patch, added_controls, removed_controls = ObjectPatch.from_diff( + None, page, control_cls=BaseControl + ) + + # 2 - convert patch to hierarchy + graph_patch = patch.to_message() + print("Patch 1:", graph_patch) + + msg = msgpack.packb( + graph_patch, default=configure_encode_object_for_msgpack(BaseControl) + ) + + print("Message 1:", msg) + + # print(page) + assert page.window.width == 800.0 + assert page.window.height == 628.0 + assert page.debug is True + # assert page._prev_debug is True + assert page.platform_brightness == Brightness.LIGHT + # assert page._prev_platform_brightness == Brightness.LIGHT + print("page.media:", page.media) + assert isinstance(page.media, PageMediaData) + assert isinstance(page.media.padding, Padding) + assert isinstance(page.media.view_insets, Padding) + assert isinstance(page.media.view_padding, Padding) + # assert page.media.padding._prev_left == 0.0 + # assert page.media.view_insets._prev_top == 0.0 + assert page.platform == PagePlatform.MACOS + + # 1 -calculate diff + patch, _, _ = ObjectPatch.from_diff(page, page, control_cls=BaseControl) + + # 2 - convert patch to hierarchy + graph_patch = patch.to_message() + print("PATCH 1:", graph_patch) + + assert graph_patch == [[0]] + + page.media.padding.left = 1 + page.platform_brightness = Brightness.DARK + page.window.width = 1024 + page.window.height = 768 + + # 1 -calculate diff + patch, _, _ = ObjectPatch.from_diff(page, page, control_cls=BaseControl) + + # 2 - convert patch to hierarchy + graph_patch = patch.to_message() + print("PATCH 2:", graph_patch) + + # TODO - fix tests + # assert graph_patch["window"]["width"] == 1024 + # assert graph_patch["platform_brightness"] == Brightness.DARK + # assert graph_patch["media"]["padding"]["left"] == 1 + + msg = msgpack.packb( + graph_patch, default=configure_encode_object_for_msgpack(BaseControl) + ) + + print("Message 2:", msg) diff --git a/sdk/python/packages/flet/tests/test_radio.py b/sdk/python/packages/flet/tests/test_radio.py deleted file mode 100644 index c78bc7584..000000000 --- a/sdk/python/packages/flet/tests/test_radio.py +++ /dev/null @@ -1,33 +0,0 @@ -from flet.core.protocol import Command - -import flet as ft - - -def test_instance_no_attrs_set(): - r = ft.Radio() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["radio"], - attrs={}, - commands=[], - ) - ], "Test failed" - - -def test_label_position_enum(): - r = ft.Radio(label_position=ft.LabelPosition.LEFT) - assert isinstance(r.label_position, ft.LabelPosition) - assert isinstance(r._get_attr("labelPosition"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["labelposition"] == "left" - - -def test_label_position_str(): - r = ft.Radio(label_position="left") - assert isinstance(r.label_position, str) - assert isinstance(r._get_attr("labelPosition"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["labelposition"] == "left" diff --git a/sdk/python/packages/flet/tests/test_responsive_row.py b/sdk/python/packages/flet/tests/test_responsive_row.py deleted file mode 100644 index ee790b0f9..000000000 --- a/sdk/python/packages/flet/tests/test_responsive_row.py +++ /dev/null @@ -1,49 +0,0 @@ -from flet.core.protocol import Command - -import flet as ft - - -def test_instance_no_attrs_set(): - r = ft.ResponsiveRow() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["responsiverow"], - attrs={}, - commands=[], - ) - ], "Test failed" - - -def test_alignment_enum(): - r = ft.ResponsiveRow(alignment=ft.MainAxisAlignment.SPACE_AROUND) - assert isinstance(r.alignment, ft.MainAxisAlignment) - assert isinstance(r._get_attr("alignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["alignment"] == "spaceAround" - - -def test_alignment_str(): - r = ft.ResponsiveRow(alignment="center") - assert isinstance(r.alignment, str) - assert isinstance(r._get_attr("alignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["alignment"] == "center" - - -def test_vertical_alignment_enum(): - r = ft.ResponsiveRow(vertical_alignment=ft.CrossAxisAlignment.STRETCH) - assert isinstance(r.vertical_alignment, ft.CrossAxisAlignment) - assert isinstance(r._get_attr("verticalAlignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["verticalalignment"] == "stretch" - - -def test_vertical_alignment_str(): - r = ft.ResponsiveRow(vertical_alignment="center") - assert isinstance(r.vertical_alignment, str) - assert isinstance(r._get_attr("verticalAlignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["verticalalignment"] == "center" diff --git a/sdk/python/packages/flet/tests/test_row.py b/sdk/python/packages/flet/tests/test_row.py deleted file mode 100644 index 4750bbdda..000000000 --- a/sdk/python/packages/flet/tests/test_row.py +++ /dev/null @@ -1,72 +0,0 @@ -from flet.core.protocol import Command - -import flet as ft - - -def test_instance_no_attrs_set(): - r = ft.Row() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["row"], - attrs={}, - commands=[], - ) - ], "Test failed" - - -def test_alignment_enum(): - r = ft.Row(alignment=ft.MainAxisAlignment.SPACE_AROUND) - assert isinstance(r.alignment, ft.MainAxisAlignment) - assert isinstance(r._get_attr("alignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["alignment"] == "spaceAround" - - -def test_alignment_str(): - r = ft.Row(alignment="center") - assert isinstance(r.alignment, str) - assert isinstance(r._get_attr("alignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["alignment"] == "center" - - -def test_vertical_alignment_enum(): - r = ft.Row(vertical_alignment=ft.CrossAxisAlignment.STRETCH) - assert isinstance(r.vertical_alignment, ft.CrossAxisAlignment) - assert isinstance(r._get_attr("verticalAlignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["verticalalignment"] == "stretch" - - -def test_vertical_alignment_str(): - r = ft.Row(vertical_alignment="center") - assert isinstance(r.vertical_alignment, str) - assert isinstance(r._get_attr("verticalAlignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["verticalalignment"] == "center" - - -def test_scroll_enum(): - r = ft.Row() - assert r.scroll is None - assert r._get_attr("scroll") is None - - r = ft.Row(scroll=ft.ScrollMode.ALWAYS) - assert isinstance(r.scroll, ft.ScrollMode) - assert r.scroll == ft.ScrollMode.ALWAYS - assert r._get_attr("scroll") == "always" - - r = ft.Row(scroll="adaptive") - assert isinstance(r.scroll, str) - assert r._get_attr("scroll") == "adaptive" - - r = ft.Row(scroll=True) - assert isinstance(r.scroll, bool) - assert r._get_attr("scroll") == "auto" - - r = ft.Row(scroll=False) - assert isinstance(r.scroll, bool) - assert r._get_attr("scroll") is None diff --git a/sdk/python/packages/flet/tests/test_shader_mask.py b/sdk/python/packages/flet/tests/test_shader_mask.py deleted file mode 100644 index 4b1e097da..000000000 --- a/sdk/python/packages/flet/tests/test_shader_mask.py +++ /dev/null @@ -1,39 +0,0 @@ -import flet as ft -from flet.core.protocol import Command - - -def test_instance_no_attrs_set(): - r = ft.ShaderMask(shader=ft.LinearGradient(colors=[ft.Colors.BLUE])) - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["shadermask"], - attrs={ - "shader": '{"colors":["blue"],"tile_mode":"clamp","begin":{"x":-1,"y":0},"end":{"x":1,"y":0},"type":"linear"}' - }, - commands=[], - ) - ], "Test failed" - - -def test_blend_mode_enum(): - r = ft.ShaderMask( - shader=ft.LinearGradient(colors=[ft.Colors.BLUE]), - blend_mode=ft.BlendMode.LIGHTEN, - ) - assert isinstance(r.blend_mode, ft.BlendMode) - assert isinstance(r._get_attr("blendMode"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["blendmode"] == "lighten" - - -def test_blend_mode_str(): - r = ft.ShaderMask( - shader=ft.LinearGradient(colors=[ft.Colors.BLUE]), blend_mode="darken" - ) - assert isinstance(r.blend_mode, str) - assert isinstance(r._get_attr("blendMode"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["blendmode"] == "darken" diff --git a/sdk/python/packages/flet/tests/test_stack.py b/sdk/python/packages/flet/tests/test_stack.py deleted file mode 100644 index ff046ab2e..000000000 --- a/sdk/python/packages/flet/tests/test_stack.py +++ /dev/null @@ -1,32 +0,0 @@ -from flet.core.protocol import Command - -import flet as ft - - -def test_instance_no_attrs_set(): - r = ft.Stack() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["stack"], - attrs={}, - commands=[], - ) - ], "Test failed" - - -def test_clip_behavior_enum(): - r = ft.Stack() - assert r.clip_behavior is None - assert r._get_attr("clipBehavior") is None - - r = ft.Stack(clip_behavior=ft.ClipBehavior.ANTI_ALIAS) - assert isinstance(r.clip_behavior, ft.ClipBehavior) - assert r.clip_behavior == ft.ClipBehavior.ANTI_ALIAS - assert r._get_attr("clipBehavior") == "antiAlias" - - r = ft.Stack(clip_behavior="none") - assert isinstance(r.clip_behavior, str) - assert r._get_attr("clipBehavior") == "none" diff --git a/sdk/python/packages/flet/tests/test_switch.py b/sdk/python/packages/flet/tests/test_switch.py deleted file mode 100644 index 06d718f85..000000000 --- a/sdk/python/packages/flet/tests/test_switch.py +++ /dev/null @@ -1,33 +0,0 @@ -from flet.core.protocol import Command - -import flet as ft - - -def test_instance_no_attrs_set(): - r = ft.Switch() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["switch"], - attrs={}, - commands=[], - ) - ], "Test failed" - - -def test_label_position_enum(): - r = ft.Switch(label_position=ft.LabelPosition.LEFT) - assert isinstance(r.label_position, ft.LabelPosition) - assert isinstance(r._get_attr("labelPosition"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["labelposition"] == "left" - - -def test_label_position_str(): - r = ft.Switch(label_position="left") - assert isinstance(r.label_position, str) - assert isinstance(r._get_attr("labelPosition"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["labelposition"] == "left" diff --git a/sdk/python/packages/flet/tests/test_text.py b/sdk/python/packages/flet/tests/test_text.py deleted file mode 100644 index b7514ab2e..000000000 --- a/sdk/python/packages/flet/tests/test_text.py +++ /dev/null @@ -1,76 +0,0 @@ -import flet as ft -from flet.core.protocol import Command - - -def test_instance_no_attrs_set(): - r = ft.Text() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["text"], - attrs={}, - commands=[], - ) - ], "Test failed" - - -def test_text_align_enum(): - r = ft.Text() - assert r.text_align is None - assert r._get_attr("textAlign") is None - - r = ft.Text(text_align=ft.TextAlign.RIGHT) - assert isinstance(r.text_align, ft.TextAlign) - assert r.text_align == ft.TextAlign.RIGHT - assert r._get_attr("textAlign") == "right" - - r = ft.Text(text_align="left") - assert isinstance(r.text_align, str) - assert r._get_attr("textAlign") == "left" - - -def test_text_style_enum(): - r = ft.Text() - assert r.style is None - assert r._get_attr("style") is None - - r = ft.Text(style=ft.TextThemeStyle.DISPLAY_LARGE) - assert isinstance(r.style, ft.TextThemeStyle) - assert r.style == ft.TextThemeStyle.DISPLAY_LARGE - assert r._get_attr("style") == "displayLarge" - - r = ft.Text(style="bodyMedium") - assert isinstance(r.style, str) - assert r._get_attr("style") == "bodyMedium" - - -def test_text_overflow_enum(): - r = ft.Text() - assert r.overflow is None - assert r._get_attr("overflow") is None - - r = ft.Text(overflow=ft.TextOverflow.ELLIPSIS) - assert isinstance(r.overflow, ft.TextOverflow) - assert r.overflow == ft.TextOverflow.ELLIPSIS - assert r._get_attr("overflow") == "ellipsis" - - r = ft.Text(overflow="fade") - assert isinstance(r.overflow, str) - assert r._get_attr("overflow") == "fade" - - -def test_weight_enum(): - r = ft.Text() - assert r.weight is None - assert r._get_attr("weight") is None - - r = ft.Text(weight=ft.FontWeight.BOLD) - assert isinstance(r.weight, ft.FontWeight) - assert r.weight == ft.FontWeight.BOLD - assert r._get_attr("weight") == "bold" - - r = ft.Text(weight="w100") - assert isinstance(r.weight, str) - assert r._get_attr("weight") == "w100" diff --git a/sdk/python/packages/flet/tests/test_textfield.py b/sdk/python/packages/flet/tests/test_textfield.py deleted file mode 100644 index d75a38215..000000000 --- a/sdk/python/packages/flet/tests/test_textfield.py +++ /dev/null @@ -1,100 +0,0 @@ -import flet as ft -from flet.core.protocol import Command - - -def test_instance_no_attrs_set(): - r = ft.TextField() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["textfield"], - attrs={}, - commands=[], - ) - ], "Test failed" - - -def test_text_align_enum(): - r = ft.TextField(text_align=ft.TextAlign.LEFT) - assert isinstance(r.text_align, ft.TextAlign) - assert isinstance(r._get_attr("textAlign"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["textalign"] == "left" - - -def test_text_align_str(): - r = ft.TextField(text_align="left") - assert isinstance(r.text_align, str) - assert isinstance(r._get_attr("textAlign"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["textalign"] == "left" - - -def test_keyboard_type_enum(): - r = ft.TextField() - assert r.keyboard_type is None - assert r._get_attr("keyboardType") is None - - r = ft.TextField(keyboard_type=ft.KeyboardType.NONE) - assert isinstance(r.keyboard_type, ft.KeyboardType) - assert r.keyboard_type == ft.KeyboardType.NONE - assert r._get_attr("keyboardType") == "none" - - r = ft.TextField(keyboard_type="phone") - assert isinstance(r.keyboard_type, str) - assert r._get_attr("keyboardType") == "phone" - - -def test_capitalization_enum(): - r = ft.TextField() - assert r.capitalization is None - assert r._get_attr("capitalization") is None - - r = ft.TextField(capitalization=ft.TextCapitalization.WORDS) - assert isinstance(r.capitalization, ft.TextCapitalization) - assert r.capitalization == ft.TextCapitalization.WORDS - assert r._get_attr("capitalization") == "words" - - r = ft.TextField(capitalization="sentences") - assert isinstance(r.capitalization, str) - assert r._get_attr("capitalization") == "sentences" - - -def test_border_enum(): - r = ft.TextField() - assert r.border is None - assert r._get_attr("border") is None - - r = ft.TextField(border=ft.InputBorder.OUTLINE) - assert isinstance(r.border, ft.InputBorder) - assert r.border == ft.InputBorder.OUTLINE - assert r._get_attr("border") == "outline" - - r = ft.TextField(border="none") - assert isinstance(r.border, str) - assert r._get_attr("border") == "none" - - -def test_bgcolor_sets_filled(): - r = ft.TextField() - r.bgcolor = ft.Colors.BLUE - cmd = r._build_add_commands() - assert r.filled is not None and r.filled - assert r._get_attr("filled") is not None and r._get_attr("filled") - - r = ft.TextField(bgcolor=ft.Colors.BLUE) - cmd = r._build_add_commands() - assert r.filled is not None and r.filled - assert r._get_attr("filled") is not None and r._get_attr("filled") - - r = ft.TextField(bgcolor=ft.Colors.BLUE, filled=True) - cmd = r._build_add_commands() - assert r.filled is not None and r.filled - assert r._get_attr("filled") is not None and r._get_attr("filled") - - r = ft.TextField(bgcolor=ft.Colors.BLUE, filled=False) - cmd = r._build_add_commands() - assert r.filled is not None and not r.filled - assert r._get_attr("filled") is not None and not r._get_attr("filled") diff --git a/sdk/python/packages/flet/tests/test_view.py b/sdk/python/packages/flet/tests/test_view.py deleted file mode 100644 index a137baed8..000000000 --- a/sdk/python/packages/flet/tests/test_view.py +++ /dev/null @@ -1,72 +0,0 @@ -from flet.core.protocol import Command - -import flet as ft - - -def test_instance_no_attrs_set(): - r = ft.View() - assert isinstance(r, ft.Control) - assert r._build_add_commands() == [ - Command( - indent=0, - name=None, - values=["view"], - attrs={}, - commands=[], - ) - ], "Test failed" - - -def test_horizontal_alignment_enum(): - r = ft.View(horizontal_alignment=ft.CrossAxisAlignment.STRETCH) - assert isinstance(r.horizontal_alignment, ft.CrossAxisAlignment) - assert isinstance(r._get_attr("horizontalAlignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["horizontalalignment"] == "stretch" - - -def test_horizontal_alignment_str(): - r = ft.View(horizontal_alignment="center") - assert isinstance(r.horizontal_alignment, str) - assert isinstance(r._get_attr("horizontalAlignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["horizontalalignment"] == "center" - - -def test_vertical_alignment_enum(): - r = ft.View(vertical_alignment=ft.MainAxisAlignment.CENTER) - assert isinstance(r.vertical_alignment, ft.MainAxisAlignment) - assert isinstance(r._get_attr("verticalAlignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["verticalalignment"] == "center" - - -def test_vertical_alignment_str(): - r = ft.View(vertical_alignment="center") - assert isinstance(r.vertical_alignment, str) - assert isinstance(r._get_attr("verticalAlignment"), str) - cmd = r._build_add_commands() - assert cmd[0].attrs["verticalalignment"] == "center" - - -def test_scroll_enum(): - r = ft.View() - assert r.scroll is None - assert r._get_attr("scroll") is None - - r = ft.View(scroll=ft.ScrollMode.ALWAYS) - assert isinstance(r.scroll, ft.ScrollMode) - assert r.scroll == ft.ScrollMode.ALWAYS - assert r._get_attr("scroll") == "always" - - r = ft.View(scroll="adaptive") - assert isinstance(r.scroll, str) - assert r._get_attr("scroll") == "adaptive" - - r = ft.View(scroll=True) - assert isinstance(r.scroll, bool) - assert r._get_attr("scroll") == "auto" - - r = ft.View(scroll=False) - assert isinstance(r.scroll, bool) - assert r._get_attr("scroll") is None diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index 10e005f0f..b2def35e6 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -29,8 +29,6 @@ dev = [ "pyinstaller >=6.6.0", "pillow >=10.3.0", "pre-commit >=2.21.0", - "hypercorn >=0.14.4", - "gunicorn >=21.2.0", "pypi-cleanup==0.1.4", "matplotlib >=3.10.1", "plotly >=6.0.1", diff --git a/sdk/python/uv.lock b/sdk/python/uv.lock index 582ab0ebd..cade953a0 100644 --- a/sdk/python/uv.lock +++ b/sdk/python/uv.lock @@ -1,12 +1,18 @@ version = 1 requires-python = ">=3.10, <3.14" resolution-markers = [ - "(python_full_version >= '3.12' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'win32')", - "(python_full_version == '3.11.*' and sys_platform == 'darwin') or (python_full_version == '3.11.*' and sys_platform == 'win32')", - "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'win32')", - "python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'win32'", - "python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'win32'", - "python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'win32'", + "(python_full_version >= '3.12' and platform_system == 'Darwin' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_system == 'Darwin' and sys_platform == 'win32') or (python_full_version >= '3.12' and platform_system == 'Windows' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_system == 'Windows' and sys_platform == 'win32')", + "(python_full_version >= '3.12' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform == 'win32')", + "(python_full_version == '3.11.*' and platform_system == 'Darwin' and sys_platform == 'darwin') or (python_full_version == '3.11.*' and platform_system == 'Darwin' and sys_platform == 'win32') or (python_full_version == '3.11.*' and platform_system == 'Windows' and sys_platform == 'darwin') or (python_full_version == '3.11.*' and platform_system == 'Windows' and sys_platform == 'win32')", + "(python_full_version == '3.11.*' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform == 'darwin') or (python_full_version == '3.11.*' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform == 'win32')", + "(python_full_version < '3.11' and platform_system == 'Darwin' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_system == 'Darwin' and sys_platform == 'win32') or (python_full_version < '3.11' and platform_system == 'Windows' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_system == 'Windows' and sys_platform == 'win32')", + "(python_full_version < '3.11' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform == 'win32')", + "(python_full_version >= '3.12' and platform_system == 'Darwin' and sys_platform != 'darwin' and sys_platform != 'win32') or (python_full_version >= '3.12' and platform_system == 'Windows' and sys_platform != 'darwin' and sys_platform != 'win32')", + "python_full_version >= '3.12' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform != 'darwin' and sys_platform != 'win32'", + "(python_full_version == '3.11.*' and platform_system == 'Darwin' and sys_platform != 'darwin' and sys_platform != 'win32') or (python_full_version == '3.11.*' and platform_system == 'Windows' and sys_platform != 'darwin' and sys_platform != 'win32')", + "python_full_version == '3.11.*' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform != 'darwin' and sys_platform != 'win32'", + "(python_full_version < '3.11' and platform_system == 'Darwin' and sys_platform != 'darwin' and sys_platform != 'win32') or (python_full_version < '3.11' and platform_system == 'Windows' and sys_platform != 'darwin' and sys_platform != 'win32')", + "python_full_version < '3.11' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform != 'darwin' and sys_platform != 'win32'", ] [manifest] @@ -78,11 +84,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.4.26" +version = "2025.6.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705 } +sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618 }, + { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650 }, ] [[package]] @@ -162,75 +168,75 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, - { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, - { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, - { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, - { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, - { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, - { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, - { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, - { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, - { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, - { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, - { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, - { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, - { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, - { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, - { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, - { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, - { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, - { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, - { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, - { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, - { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, - { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, - { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, - { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, - { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, - { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, - { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, - { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, - { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, - { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, - { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, - { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, - { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, - { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, - { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, - { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, - { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, - { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, - { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, - { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, - { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, - { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, - { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, - { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, - { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, - { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, - { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, - { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, - { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818 }, + { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649 }, + { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045 }, + { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356 }, + { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471 }, + { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317 }, + { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368 }, + { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491 }, + { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695 }, + { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849 }, + { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091 }, + { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445 }, + { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782 }, + { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794 }, + { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846 }, + { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350 }, + { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657 }, + { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260 }, + { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164 }, + { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571 }, + { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952 }, + { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959 }, + { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030 }, + { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015 }, + { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106 }, + { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402 }, + { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936 }, + { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790 }, + { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924 }, + { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626 }, + { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567 }, + { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957 }, + { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408 }, + { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399 }, + { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815 }, + { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537 }, + { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565 }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357 }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776 }, + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622 }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435 }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653 }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231 }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243 }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442 }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147 }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057 }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454 }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174 }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166 }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064 }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641 }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626 }, ] [[package]] name = "click" -version = "8.1.8" +version = "8.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "colorama", marker = "platform_system == 'Windows'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 }, ] [[package]] @@ -247,7 +253,8 @@ name = "contourpy" version = "1.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130 } wheels = [ @@ -330,47 +337,49 @@ wheels = [ [[package]] name = "cryptography" -version = "44.0.2" +version = "45.0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/25/4ce80c78963834b8a9fd1cc1266be5ed8d1840785c0f2e1b73b8d128d505/cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0", size = 710807 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/ef/83e632cfa801b221570c5f58c0369db6fa6cef7d9ff859feab1aae1a8a0f/cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7", size = 6676361 }, - { url = "https://files.pythonhosted.org/packages/30/ec/7ea7c1e4c8fc8329506b46c6c4a52e2f20318425d48e0fe597977c71dbce/cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1", size = 3952350 }, - { url = "https://files.pythonhosted.org/packages/27/61/72e3afdb3c5ac510330feba4fc1faa0fe62e070592d6ad00c40bb69165e5/cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb", size = 4166572 }, - { url = "https://files.pythonhosted.org/packages/26/e4/ba680f0b35ed4a07d87f9e98f3ebccb05091f3bf6b5a478b943253b3bbd5/cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843", size = 3958124 }, - { url = "https://files.pythonhosted.org/packages/9c/e8/44ae3e68c8b6d1cbc59040288056df2ad7f7f03bbcaca6b503c737ab8e73/cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5", size = 3678122 }, - { url = "https://files.pythonhosted.org/packages/27/7b/664ea5e0d1eab511a10e480baf1c5d3e681c7d91718f60e149cec09edf01/cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c", size = 4191831 }, - { url = "https://files.pythonhosted.org/packages/2a/07/79554a9c40eb11345e1861f46f845fa71c9e25bf66d132e123d9feb8e7f9/cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a", size = 3960583 }, - { url = "https://files.pythonhosted.org/packages/bb/6d/858e356a49a4f0b591bd6789d821427de18432212e137290b6d8a817e9bf/cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308", size = 4191753 }, - { url = "https://files.pythonhosted.org/packages/b2/80/62df41ba4916067fa6b125aa8c14d7e9181773f0d5d0bd4dcef580d8b7c6/cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688", size = 4079550 }, - { url = "https://files.pythonhosted.org/packages/f3/cd/2558cc08f7b1bb40683f99ff4327f8dcfc7de3affc669e9065e14824511b/cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7", size = 4298367 }, - { url = "https://files.pythonhosted.org/packages/71/59/94ccc74788945bc3bd4cf355d19867e8057ff5fdbcac781b1ff95b700fb1/cryptography-44.0.2-cp37-abi3-win32.whl", hash = "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79", size = 2772843 }, - { url = "https://files.pythonhosted.org/packages/ca/2c/0d0bbaf61ba05acb32f0841853cfa33ebb7a9ab3d9ed8bb004bd39f2da6a/cryptography-44.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa", size = 3209057 }, - { url = "https://files.pythonhosted.org/packages/9e/be/7a26142e6d0f7683d8a382dd963745e65db895a79a280a30525ec92be890/cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3", size = 6677789 }, - { url = "https://files.pythonhosted.org/packages/06/88/638865be7198a84a7713950b1db7343391c6066a20e614f8fa286eb178ed/cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639", size = 3951919 }, - { url = "https://files.pythonhosted.org/packages/d7/fc/99fe639bcdf58561dfad1faa8a7369d1dc13f20acd78371bb97a01613585/cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd", size = 4167812 }, - { url = "https://files.pythonhosted.org/packages/53/7b/aafe60210ec93d5d7f552592a28192e51d3c6b6be449e7fd0a91399b5d07/cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181", size = 3958571 }, - { url = "https://files.pythonhosted.org/packages/16/32/051f7ce79ad5a6ef5e26a92b37f172ee2d6e1cce09931646eef8de1e9827/cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea", size = 3679832 }, - { url = "https://files.pythonhosted.org/packages/78/2b/999b2a1e1ba2206f2d3bca267d68f350beb2b048a41ea827e08ce7260098/cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699", size = 4193719 }, - { url = "https://files.pythonhosted.org/packages/72/97/430e56e39a1356e8e8f10f723211a0e256e11895ef1a135f30d7d40f2540/cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9", size = 3960852 }, - { url = "https://files.pythonhosted.org/packages/89/33/c1cf182c152e1d262cac56850939530c05ca6c8d149aa0dcee490b417e99/cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23", size = 4193906 }, - { url = "https://files.pythonhosted.org/packages/e1/99/87cf26d4f125380dc674233971069bc28d19b07f7755b29861570e513650/cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922", size = 4081572 }, - { url = "https://files.pythonhosted.org/packages/b3/9f/6a3e0391957cc0c5f84aef9fbdd763035f2b52e998a53f99345e3ac69312/cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4", size = 4298631 }, - { url = "https://files.pythonhosted.org/packages/e2/a5/5bc097adb4b6d22a24dea53c51f37e480aaec3465285c253098642696423/cryptography-44.0.2-cp39-abi3-win32.whl", hash = "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5", size = 2773792 }, - { url = "https://files.pythonhosted.org/packages/33/cf/1f7649b8b9a3543e042d3f348e398a061923ac05b507f3f4d95f11938aa9/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", size = 3210957 }, - { url = "https://files.pythonhosted.org/packages/99/10/173be140714d2ebaea8b641ff801cbcb3ef23101a2981cbf08057876f89e/cryptography-44.0.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:af4ff3e388f2fa7bff9f7f2b31b87d5651c45731d3e8cfa0944be43dff5cfbdb", size = 3396886 }, - { url = "https://files.pythonhosted.org/packages/2f/b4/424ea2d0fce08c24ede307cead3409ecbfc2f566725d4701b9754c0a1174/cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0529b1d5a0105dd3731fa65680b45ce49da4d8115ea76e9da77a875396727b41", size = 3892387 }, - { url = "https://files.pythonhosted.org/packages/28/20/8eaa1a4f7c68a1cb15019dbaad59c812d4df4fac6fd5f7b0b9c5177f1edd/cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7ca25849404be2f8e4b3c59483d9d3c51298a22c1c61a0e84415104dacaf5562", size = 4109922 }, - { url = "https://files.pythonhosted.org/packages/11/25/5ed9a17d532c32b3bc81cc294d21a36c772d053981c22bd678396bc4ae30/cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:268e4e9b177c76d569e8a145a6939eca9a5fec658c932348598818acf31ae9a5", size = 3895715 }, - { url = "https://files.pythonhosted.org/packages/63/31/2aac03b19c6329b62c45ba4e091f9de0b8f687e1b0cd84f101401bece343/cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:9eb9d22b0a5d8fd9925a7764a054dca914000607dff201a24c791ff5c799e1fa", size = 4109876 }, - { url = "https://files.pythonhosted.org/packages/99/ec/6e560908349843718db1a782673f36852952d52a55ab14e46c42c8a7690a/cryptography-44.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2bf7bf75f7df9715f810d1b038870309342bff3069c5bd8c6b96128cb158668d", size = 3131719 }, - { url = "https://files.pythonhosted.org/packages/d6/d7/f30e75a6aa7d0f65031886fa4a1485c2fbfe25a1896953920f6a9cfe2d3b/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:909c97ab43a9c0c0b0ada7a1281430e4e5ec0458e6d9244c0e821bbf152f061d", size = 3887513 }, - { url = "https://files.pythonhosted.org/packages/9c/b4/7a494ce1032323ca9db9a3661894c66e0d7142ad2079a4249303402d8c71/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96e7a5e9d6e71f9f4fca8eebfd603f8e86c5225bb18eb621b2c1e50b290a9471", size = 4107432 }, - { url = "https://files.pythonhosted.org/packages/45/f8/6b3ec0bc56123b344a8d2b3264a325646d2dcdbdd9848b5e6f3d37db90b3/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d1b3031093a366ac767b3feb8bcddb596671b3aaff82d4050f984da0c248b615", size = 3891421 }, - { url = "https://files.pythonhosted.org/packages/57/ff/f3b4b2d007c2a646b0f69440ab06224f9cf37a977a72cdb7b50632174e8a/cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:04abd71114848aa25edb28e225ab5f268096f44cf0127f3d36975bdf1bdf3390", size = 4107081 }, +sdist = { url = "https://files.pythonhosted.org/packages/fe/c8/a2a376a8711c1e11708b9c9972e0c3223f5fc682552c82d8db844393d6ce/cryptography-45.0.4.tar.gz", hash = "sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57", size = 744890 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/1c/92637793de053832523b410dbe016d3f5c11b41d0cf6eef8787aabb51d41/cryptography-45.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:425a9a6ac2823ee6e46a76a21a4e8342d8fa5c01e08b823c1f19a8b74f096069", size = 7055712 }, + { url = "https://files.pythonhosted.org/packages/ba/14/93b69f2af9ba832ad6618a03f8a034a5851dc9a3314336a3d71c252467e1/cryptography-45.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:680806cf63baa0039b920f4976f5f31b10e772de42f16310a6839d9f21a26b0d", size = 4205335 }, + { url = "https://files.pythonhosted.org/packages/67/30/fae1000228634bf0b647fca80403db5ca9e3933b91dd060570689f0bd0f7/cryptography-45.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4ca0f52170e821bc8da6fc0cc565b7bb8ff8d90d36b5e9fdd68e8a86bdf72036", size = 4431487 }, + { url = "https://files.pythonhosted.org/packages/6d/5a/7dffcf8cdf0cb3c2430de7404b327e3db64735747d641fc492539978caeb/cryptography-45.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f3fe7a5ae34d5a414957cc7f457e2b92076e72938423ac64d215722f6cf49a9e", size = 4208922 }, + { url = "https://files.pythonhosted.org/packages/c6/f3/528729726eb6c3060fa3637253430547fbaaea95ab0535ea41baa4a6fbd8/cryptography-45.0.4-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:25eb4d4d3e54595dc8adebc6bbd5623588991d86591a78c2548ffb64797341e2", size = 3900433 }, + { url = "https://files.pythonhosted.org/packages/d9/4a/67ba2e40f619e04d83c32f7e1d484c1538c0800a17c56a22ff07d092ccc1/cryptography-45.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce1678a2ccbe696cf3af15a75bb72ee008d7ff183c9228592ede9db467e64f1b", size = 4464163 }, + { url = "https://files.pythonhosted.org/packages/7e/9a/b4d5aa83661483ac372464809c4b49b5022dbfe36b12fe9e323ca8512420/cryptography-45.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:49fe9155ab32721b9122975e168a6760d8ce4cffe423bcd7ca269ba41b5dfac1", size = 4208687 }, + { url = "https://files.pythonhosted.org/packages/db/b7/a84bdcd19d9c02ec5807f2ec2d1456fd8451592c5ee353816c09250e3561/cryptography-45.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2882338b2a6e0bd337052e8b9007ced85c637da19ef9ecaf437744495c8c2999", size = 4463623 }, + { url = "https://files.pythonhosted.org/packages/d8/84/69707d502d4d905021cac3fb59a316344e9f078b1da7fb43ecde5e10840a/cryptography-45.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:23b9c3ea30c3ed4db59e7b9619272e94891f8a3a5591d0b656a7582631ccf750", size = 4332447 }, + { url = "https://files.pythonhosted.org/packages/f3/ee/d4f2ab688e057e90ded24384e34838086a9b09963389a5ba6854b5876598/cryptography-45.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0a97c927497e3bc36b33987abb99bf17a9a175a19af38a892dc4bbb844d7ee2", size = 4572830 }, + { url = "https://files.pythonhosted.org/packages/70/d4/994773a261d7ff98034f72c0e8251fe2755eac45e2265db4c866c1c6829c/cryptography-45.0.4-cp311-abi3-win32.whl", hash = "sha256:e00a6c10a5c53979d6242f123c0a97cff9f3abed7f064fc412c36dc521b5f257", size = 2932769 }, + { url = "https://files.pythonhosted.org/packages/5a/42/c80bd0b67e9b769b364963b5252b17778a397cefdd36fa9aa4a5f34c599a/cryptography-45.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:817ee05c6c9f7a69a16200f0c90ab26d23a87701e2a284bd15156783e46dbcc8", size = 3410441 }, + { url = "https://files.pythonhosted.org/packages/ce/0b/2488c89f3a30bc821c9d96eeacfcab6ff3accc08a9601ba03339c0fd05e5/cryptography-45.0.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:964bcc28d867e0f5491a564b7debb3ffdd8717928d315d12e0d7defa9e43b723", size = 7031836 }, + { url = "https://files.pythonhosted.org/packages/fe/51/8c584ed426093aac257462ae62d26ad61ef1cbf5b58d8b67e6e13c39960e/cryptography-45.0.4-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6a5bf57554e80f75a7db3d4b1dacaa2764611ae166ab42ea9a72bcdb5d577637", size = 4195746 }, + { url = "https://files.pythonhosted.org/packages/5c/7d/4b0ca4d7af95a704eef2f8f80a8199ed236aaf185d55385ae1d1610c03c2/cryptography-45.0.4-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:46cf7088bf91bdc9b26f9c55636492c1cce3e7aaf8041bbf0243f5e5325cfb2d", size = 4424456 }, + { url = "https://files.pythonhosted.org/packages/1d/45/5fabacbc6e76ff056f84d9f60eeac18819badf0cefc1b6612ee03d4ab678/cryptography-45.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7bedbe4cc930fa4b100fc845ea1ea5788fcd7ae9562e669989c11618ae8d76ee", size = 4198495 }, + { url = "https://files.pythonhosted.org/packages/55/b7/ffc9945b290eb0a5d4dab9b7636706e3b5b92f14ee5d9d4449409d010d54/cryptography-45.0.4-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eaa3e28ea2235b33220b949c5a0d6cf79baa80eab2eb5607ca8ab7525331b9ff", size = 3885540 }, + { url = "https://files.pythonhosted.org/packages/7f/e3/57b010282346980475e77d414080acdcb3dab9a0be63071efc2041a2c6bd/cryptography-45.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7ef2dde4fa9408475038fc9aadfc1fb2676b174e68356359632e980c661ec8f6", size = 4452052 }, + { url = "https://files.pythonhosted.org/packages/37/e6/ddc4ac2558bf2ef517a358df26f45bc774a99bf4653e7ee34b5e749c03e3/cryptography-45.0.4-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6a3511ae33f09094185d111160fd192c67aa0a2a8d19b54d36e4c78f651dc5ad", size = 4198024 }, + { url = "https://files.pythonhosted.org/packages/3a/c0/85fa358ddb063ec588aed4a6ea1df57dc3e3bc1712d87c8fa162d02a65fc/cryptography-45.0.4-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:06509dc70dd71fa56eaa138336244e2fbaf2ac164fc9b5e66828fccfd2b680d6", size = 4451442 }, + { url = "https://files.pythonhosted.org/packages/33/67/362d6ec1492596e73da24e669a7fbbaeb1c428d6bf49a29f7a12acffd5dc/cryptography-45.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5f31e6b0a5a253f6aa49be67279be4a7e5a4ef259a9f33c69f7d1b1191939872", size = 4325038 }, + { url = "https://files.pythonhosted.org/packages/53/75/82a14bf047a96a1b13ebb47fb9811c4f73096cfa2e2b17c86879687f9027/cryptography-45.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:944e9ccf67a9594137f942d5b52c8d238b1b4e46c7a0c2891b7ae6e01e7c80a4", size = 4560964 }, + { url = "https://files.pythonhosted.org/packages/cd/37/1a3cba4c5a468ebf9b95523a5ef5651244693dc712001e276682c278fc00/cryptography-45.0.4-cp37-abi3-win32.whl", hash = "sha256:c22fe01e53dc65edd1945a2e6f0015e887f84ced233acecb64b4daadb32f5c97", size = 2924557 }, + { url = "https://files.pythonhosted.org/packages/2a/4b/3256759723b7e66380397d958ca07c59cfc3fb5c794fb5516758afd05d41/cryptography-45.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:627ba1bc94f6adf0b0a2e35d87020285ead22d9f648c7e75bb64f367375f3b22", size = 3395508 }, + { url = "https://files.pythonhosted.org/packages/16/33/b38e9d372afde56906a23839302c19abdac1c505bfb4776c1e4b07c3e145/cryptography-45.0.4-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a77c6fb8d76e9c9f99f2f3437c1a4ac287b34eaf40997cfab1e9bd2be175ac39", size = 3580103 }, + { url = "https://files.pythonhosted.org/packages/c4/b9/357f18064ec09d4807800d05a48f92f3b369056a12f995ff79549fbb31f1/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7aad98a25ed8ac917fdd8a9c1e706e5a0956e06c498be1f713b61734333a4507", size = 4143732 }, + { url = "https://files.pythonhosted.org/packages/c4/9c/7f7263b03d5db329093617648b9bd55c953de0b245e64e866e560f9aac07/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3530382a43a0e524bc931f187fc69ef4c42828cf7d7f592f7f249f602b5a4ab0", size = 4385424 }, + { url = "https://files.pythonhosted.org/packages/a6/5a/6aa9d8d5073d5acc0e04e95b2860ef2684b2bd2899d8795fc443013e263b/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:6b613164cb8425e2f8db5849ffb84892e523bf6d26deb8f9bb76ae86181fa12b", size = 4142438 }, + { url = "https://files.pythonhosted.org/packages/42/1c/71c638420f2cdd96d9c2b287fec515faf48679b33a2b583d0f1eda3a3375/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:96d4819e25bf3b685199b304a0029ce4a3caf98947ce8a066c9137cc78ad2c58", size = 4384622 }, + { url = "https://files.pythonhosted.org/packages/ef/ab/e3a055c34e97deadbf0d846e189237d3385dca99e1a7e27384c3b2292041/cryptography-45.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b97737a3ffbea79eebb062eb0d67d72307195035332501722a9ca86bab9e3ab2", size = 3328911 }, + { url = "https://files.pythonhosted.org/packages/ea/ba/cf442ae99ef363855ed84b39e0fb3c106ac66b7a7703f3c9c9cfe05412cb/cryptography-45.0.4-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4828190fb6c4bcb6ebc6331f01fe66ae838bb3bd58e753b59d4b22eb444b996c", size = 3590512 }, + { url = "https://files.pythonhosted.org/packages/28/9a/a7d5bb87d149eb99a5abdc69a41e4e47b8001d767e5f403f78bfaafc7aa7/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:03dbff8411206713185b8cebe31bc5c0eb544799a50c09035733716b386e61a4", size = 4146899 }, + { url = "https://files.pythonhosted.org/packages/17/11/9361c2c71c42cc5c465cf294c8030e72fb0c87752bacbd7a3675245e3db3/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51dfbd4d26172d31150d84c19bbe06c68ea4b7f11bbc7b3a5e146b367c311349", size = 4388900 }, + { url = "https://files.pythonhosted.org/packages/c0/76/f95b83359012ee0e670da3e41c164a0c256aeedd81886f878911581d852f/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:0339a692de47084969500ee455e42c58e449461e0ec845a34a6a9b9bf7df7fb8", size = 4146422 }, + { url = "https://files.pythonhosted.org/packages/09/ad/5429fcc4def93e577a5407988f89cf15305e64920203d4ac14601a9dc876/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:0cf13c77d710131d33e63626bd55ae7c0efb701ebdc2b3a7952b9b23a0412862", size = 4388475 }, + { url = "https://files.pythonhosted.org/packages/99/49/0ab9774f64555a1b50102757811508f5ace451cf5dc0a2d074a4b9deca6a/cryptography-45.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bbc505d1dc469ac12a0a064214879eac6294038d6b24ae9f71faae1448a9608d", size = 3337594 }, ] [[package]] @@ -393,25 +402,28 @@ wheels = [ [[package]] name = "exceptiongroup" -version = "1.2.2" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 }, ] [[package]] name = "fastapi" -version = "0.115.12" +version = "0.115.13" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236 } +sdist = { url = "https://files.pythonhosted.org/packages/20/64/ec0788201b5554e2a87c49af26b77a4d132f807a0fa9675257ac92c6aa0e/fastapi-0.115.13.tar.gz", hash = "sha256:55d1d25c2e1e0a0a50aceb1c8705cd932def273c102bff0b1c1da88b3c6eb307", size = 295680 } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164 }, + { url = "https://files.pythonhosted.org/packages/59/4a/e17764385382062b0edbb35a26b7cf76d71e27e456546277a42ba6545c6e/fastapi-0.115.13-py3-none-any.whl", hash = "sha256:0a0cab59afa7bab22f5eb347f8c9864b681558c278395e94035a741fc10cd865", size = 95315 }, ] [[package]] @@ -454,7 +466,7 @@ web = [ requires-dist = [ { name = "flet-cli", marker = "extra == 'all'", editable = "packages/flet-cli" }, { name = "flet-cli", marker = "extra == 'cli'", editable = "packages/flet-cli" }, - { name = "flet-desktop", marker = "(sys_platform == 'darwin' and extra == 'desktop') or (sys_platform == 'win32' and extra == 'desktop')", editable = "packages/flet-desktop" }, + { name = "flet-desktop", marker = "(platform_system == 'Darwin' and extra == 'desktop') or (platform_system == 'Windows' and extra == 'desktop')", editable = "packages/flet-desktop" }, { name = "flet-desktop", marker = "extra == 'all'", editable = "packages/flet-desktop" }, { name = "flet-desktop", marker = "extra == 'desktop'", editable = "packages/flet-desktop" }, { name = "flet-web", marker = "extra == 'all'", editable = "packages/flet-web" }, @@ -513,8 +525,6 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "cryptography" }, - { name = "gunicorn" }, - { name = "hypercorn" }, { name = "matplotlib" }, { name = "pandas" }, { name = "pillow" }, @@ -538,8 +548,6 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "cryptography", specifier = ">=39.0.0" }, - { name = "gunicorn", specifier = ">=21.2.0" }, - { name = "hypercorn", specifier = ">=0.14.4" }, { name = "matplotlib", specifier = ">=3.10.1" }, { name = "pandas", specifier = ">=2.2.3" }, { name = "pillow", specifier = ">=10.3.0" }, @@ -571,55 +579,43 @@ requires-dist = [ [[package]] name = "fonttools" -version = "4.57.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/2d/a9a0b6e3a0cf6bd502e64fc16d894269011930cabfc89aee20d1635b1441/fonttools-4.57.0.tar.gz", hash = "sha256:727ece10e065be2f9dd239d15dd5d60a66e17eac11aea47d447f9f03fdbc42de", size = 3492448 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/17/3ddfd1881878b3f856065130bb603f5922e81ae8a4eb53bce0ea78f765a8/fonttools-4.57.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:babe8d1eb059a53e560e7bf29f8e8f4accc8b6cfb9b5fd10e485bde77e71ef41", size = 2756260 }, - { url = "https://files.pythonhosted.org/packages/26/2b/6957890c52c030b0bf9e0add53e5badab4682c6ff024fac9a332bb2ae063/fonttools-4.57.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81aa97669cd726349eb7bd43ca540cf418b279ee3caba5e2e295fb4e8f841c02", size = 2284691 }, - { url = "https://files.pythonhosted.org/packages/cc/8e/c043b4081774e5eb06a834cedfdb7d432b4935bc8c4acf27207bdc34dfc4/fonttools-4.57.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0e9618630edd1910ad4f07f60d77c184b2f572c8ee43305ea3265675cbbfe7e", size = 4566077 }, - { url = "https://files.pythonhosted.org/packages/59/bc/e16ae5d9eee6c70830ce11d1e0b23d6018ddfeb28025fda092cae7889c8b/fonttools-4.57.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34687a5d21f1d688d7d8d416cb4c5b9c87fca8a1797ec0d74b9fdebfa55c09ab", size = 4608729 }, - { url = "https://files.pythonhosted.org/packages/25/13/e557bf10bb38e4e4c436d3a9627aadf691bc7392ae460910447fda5fad2b/fonttools-4.57.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69ab81b66ebaa8d430ba56c7a5f9abe0183afefd3a2d6e483060343398b13fb1", size = 4759646 }, - { url = "https://files.pythonhosted.org/packages/bc/c9/5e2952214d4a8e31026bf80beb18187199b7001e60e99a6ce19773249124/fonttools-4.57.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d639397de852f2ccfb3134b152c741406752640a266d9c1365b0f23d7b88077f", size = 4941652 }, - { url = "https://files.pythonhosted.org/packages/df/04/e80242b3d9ec91a1f785d949edc277a13ecfdcfae744de4b170df9ed77d8/fonttools-4.57.0-cp310-cp310-win32.whl", hash = "sha256:cc066cb98b912f525ae901a24cd381a656f024f76203bc85f78fcc9e66ae5aec", size = 2159432 }, - { url = "https://files.pythonhosted.org/packages/33/ba/e858cdca275daf16e03c0362aa43734ea71104c3b356b2100b98543dba1b/fonttools-4.57.0-cp310-cp310-win_amd64.whl", hash = "sha256:7a64edd3ff6a7f711a15bd70b4458611fb240176ec11ad8845ccbab4fe6745db", size = 2203869 }, - { url = "https://files.pythonhosted.org/packages/81/1f/e67c99aa3c6d3d2f93d956627e62a57ae0d35dc42f26611ea2a91053f6d6/fonttools-4.57.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3871349303bdec958360eedb619169a779956503ffb4543bb3e6211e09b647c4", size = 2757392 }, - { url = "https://files.pythonhosted.org/packages/aa/f1/f75770d0ddc67db504850898d96d75adde238c35313409bfcd8db4e4a5fe/fonttools-4.57.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c59375e85126b15a90fcba3443eaac58f3073ba091f02410eaa286da9ad80ed8", size = 2285609 }, - { url = "https://files.pythonhosted.org/packages/f5/d3/bc34e4953cb204bae0c50b527307dce559b810e624a733351a654cfc318e/fonttools-4.57.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967b65232e104f4b0f6370a62eb33089e00024f2ce143aecbf9755649421c683", size = 4873292 }, - { url = "https://files.pythonhosted.org/packages/41/b8/d5933559303a4ab18c799105f4c91ee0318cc95db4a2a09e300116625e7a/fonttools-4.57.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39acf68abdfc74e19de7485f8f7396fa4d2418efea239b7061d6ed6a2510c746", size = 4902503 }, - { url = "https://files.pythonhosted.org/packages/32/13/acb36bfaa316f481153ce78de1fa3926a8bad42162caa3b049e1afe2408b/fonttools-4.57.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d077f909f2343daf4495ba22bb0e23b62886e8ec7c109ee8234bdbd678cf344", size = 5077351 }, - { url = "https://files.pythonhosted.org/packages/b5/23/6d383a2ca83b7516d73975d8cca9d81a01acdcaa5e4db8579e4f3de78518/fonttools-4.57.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:46370ac47a1e91895d40e9ad48effbe8e9d9db1a4b80888095bc00e7beaa042f", size = 5275067 }, - { url = "https://files.pythonhosted.org/packages/bc/ca/31b8919c6da0198d5d522f1d26c980201378c087bdd733a359a1e7485769/fonttools-4.57.0-cp311-cp311-win32.whl", hash = "sha256:ca2aed95855506b7ae94e8f1f6217b7673c929e4f4f1217bcaa236253055cb36", size = 2158263 }, - { url = "https://files.pythonhosted.org/packages/13/4c/de2612ea2216eb45cfc8eb91a8501615dd87716feaf5f8fb65cbca576289/fonttools-4.57.0-cp311-cp311-win_amd64.whl", hash = "sha256:17168a4670bbe3775f3f3f72d23ee786bd965395381dfbb70111e25e81505b9d", size = 2204968 }, - { url = "https://files.pythonhosted.org/packages/cb/98/d4bc42d43392982eecaaca117d79845734d675219680cd43070bb001bc1f/fonttools-4.57.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:889e45e976c74abc7256d3064aa7c1295aa283c6bb19810b9f8b604dfe5c7f31", size = 2751824 }, - { url = "https://files.pythonhosted.org/packages/1a/62/7168030eeca3742fecf45f31e63b5ef48969fa230a672216b805f1d61548/fonttools-4.57.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0425c2e052a5f1516c94e5855dbda706ae5a768631e9fcc34e57d074d1b65b92", size = 2283072 }, - { url = "https://files.pythonhosted.org/packages/5d/82/121a26d9646f0986ddb35fbbaf58ef791c25b59ecb63ffea2aab0099044f/fonttools-4.57.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44c26a311be2ac130f40a96769264809d3b0cb297518669db437d1cc82974888", size = 4788020 }, - { url = "https://files.pythonhosted.org/packages/5b/26/e0f2fb662e022d565bbe280a3cfe6dafdaabf58889ff86fdef2d31ff1dde/fonttools-4.57.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c41ba992df5b8d680b89fd84c6a1f2aca2b9f1ae8a67400c8930cd4ea115f6", size = 4859096 }, - { url = "https://files.pythonhosted.org/packages/9e/44/9075e323347b1891cdece4b3f10a3b84a8f4c42a7684077429d9ce842056/fonttools-4.57.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea1e9e43ca56b0c12440a7c689b1350066595bebcaa83baad05b8b2675129d98", size = 4964356 }, - { url = "https://files.pythonhosted.org/packages/48/28/caa8df32743462fb966be6de6a79d7f30393859636d7732e82efa09fbbb4/fonttools-4.57.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84fd56c78d431606332a0627c16e2a63d243d0d8b05521257d77c6529abe14d8", size = 5226546 }, - { url = "https://files.pythonhosted.org/packages/f6/46/95ab0f0d2e33c5b1a4fc1c0efe5e286ba9359602c0a9907adb1faca44175/fonttools-4.57.0-cp312-cp312-win32.whl", hash = "sha256:f4376819c1c778d59e0a31db5dc6ede854e9edf28bbfa5b756604727f7f800ac", size = 2146776 }, - { url = "https://files.pythonhosted.org/packages/06/5d/1be5424bb305880e1113631f49a55ea7c7da3a5fe02608ca7c16a03a21da/fonttools-4.57.0-cp312-cp312-win_amd64.whl", hash = "sha256:57e30241524879ea10cdf79c737037221f77cc126a8cdc8ff2c94d4a522504b9", size = 2193956 }, - { url = "https://files.pythonhosted.org/packages/e9/2f/11439f3af51e4bb75ac9598c29f8601aa501902dcedf034bdc41f47dd799/fonttools-4.57.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:408ce299696012d503b714778d89aa476f032414ae57e57b42e4b92363e0b8ef", size = 2739175 }, - { url = "https://files.pythonhosted.org/packages/25/52/677b55a4c0972dc3820c8dba20a29c358197a78229daa2ea219fdb19e5d5/fonttools-4.57.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bbceffc80aa02d9e8b99f2a7491ed8c4a783b2fc4020119dc405ca14fb5c758c", size = 2276583 }, - { url = "https://files.pythonhosted.org/packages/64/79/184555f8fa77b827b9460a4acdbbc0b5952bb6915332b84c615c3a236826/fonttools-4.57.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f022601f3ee9e1f6658ed6d184ce27fa5216cee5b82d279e0f0bde5deebece72", size = 4766437 }, - { url = "https://files.pythonhosted.org/packages/f8/ad/c25116352f456c0d1287545a7aa24e98987b6d99c5b0456c4bd14321f20f/fonttools-4.57.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dea5893b58d4637ffa925536462ba626f8a1b9ffbe2f5c272cdf2c6ebadb817", size = 4838431 }, - { url = "https://files.pythonhosted.org/packages/53/ae/398b2a833897297797a44f519c9af911c2136eb7aa27d3f1352c6d1129fa/fonttools-4.57.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dff02c5c8423a657c550b48231d0a48d7e2b2e131088e55983cfe74ccc2c7cc9", size = 4951011 }, - { url = "https://files.pythonhosted.org/packages/b7/5d/7cb31c4bc9ffb9a2bbe8b08f8f53bad94aeb158efad75da645b40b62cb73/fonttools-4.57.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:767604f244dc17c68d3e2dbf98e038d11a18abc078f2d0f84b6c24571d9c0b13", size = 5205679 }, - { url = "https://files.pythonhosted.org/packages/4c/e4/6934513ec2c4d3d69ca1bc3bd34d5c69dafcbf68c15388dd3bb062daf345/fonttools-4.57.0-cp313-cp313-win32.whl", hash = "sha256:8e2e12d0d862f43d51e5afb8b9751c77e6bec7d2dc00aad80641364e9df5b199", size = 2144833 }, - { url = "https://files.pythonhosted.org/packages/c4/0d/2177b7fdd23d017bcfb702fd41e47d4573766b9114da2fddbac20dcc4957/fonttools-4.57.0-cp313-cp313-win_amd64.whl", hash = "sha256:f1d6bc9c23356908db712d282acb3eebd4ae5ec6d8b696aa40342b1d84f8e9e3", size = 2190799 }, - { url = "https://files.pythonhosted.org/packages/90/27/45f8957c3132917f91aaa56b700bcfc2396be1253f685bd5c68529b6f610/fonttools-4.57.0-py3-none-any.whl", hash = "sha256:3122c604a675513c68bd24c6a8f9091f1c2376d18e8f5fe5a101746c81b3e98f", size = 1093605 }, -] - -[[package]] -name = "gunicorn" -version = "23.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029 }, +version = "4.58.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/5a/1124b2c8cb3a8015faf552e92714040bcdbc145dfa29928891b02d147a18/fonttools-4.58.4.tar.gz", hash = "sha256:928a8009b9884ed3aae17724b960987575155ca23c6f0b8146e400cc9e0d44ba", size = 3525026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/86/d22c24caa574449b56e994ed1a96d23b23af85557fb62a92df96439d3f6c/fonttools-4.58.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:834542f13fee7625ad753b2db035edb674b07522fcbdd0ed9e9a9e2a1034467f", size = 2748349 }, + { url = "https://files.pythonhosted.org/packages/f9/b8/384aca93856def00e7de30341f1e27f439694857d82c35d74a809c705ed0/fonttools-4.58.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2e6c61ce330142525296170cd65666e46121fc0d44383cbbcfa39cf8f58383df", size = 2318565 }, + { url = "https://files.pythonhosted.org/packages/1a/f2/273edfdc8d9db89ecfbbf659bd894f7e07b6d53448b19837a4bdba148d17/fonttools-4.58.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9c75f8faa29579c0fbf29b56ae6a3660c6c025f3b671803cb6a9caa7e4e3a98", size = 4838855 }, + { url = "https://files.pythonhosted.org/packages/13/fa/403703548c093c30b52ab37e109b369558afa221130e67f06bef7513f28a/fonttools-4.58.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:88dedcedbd5549e35b2ea3db3de02579c27e62e51af56779c021e7b33caadd0e", size = 4767637 }, + { url = "https://files.pythonhosted.org/packages/6e/a8/3380e1e0bff6defb0f81c9abf274a5b4a0f30bc8cab4fd4e346c6f923b4c/fonttools-4.58.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae80a895adab43586f4da1521d58fd4f4377cef322ee0cc205abcefa3a5effc3", size = 4819397 }, + { url = "https://files.pythonhosted.org/packages/cd/1b/99e47eb17a8ca51d808622a4658584fa8f340857438a4e9d7ac326d4a041/fonttools-4.58.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0d3acc7f0d151da116e87a182aefb569cf0a3c8e0fd4c9cd0a7c1e7d3e7adb26", size = 4926641 }, + { url = "https://files.pythonhosted.org/packages/31/75/415254408f038e35b36c8525fc31feb8561f98445688dd2267c23eafd7a2/fonttools-4.58.4-cp310-cp310-win32.whl", hash = "sha256:1244f69686008e7e8d2581d9f37eef330a73fee3843f1107993eb82c9d306577", size = 2201917 }, + { url = "https://files.pythonhosted.org/packages/c5/69/f019a15ed2946317c5318e1bcc8876f8a54a313848604ad1d4cfc4c07916/fonttools-4.58.4-cp310-cp310-win_amd64.whl", hash = "sha256:2a66c0af8a01eb2b78645af60f3b787de5fe5eb1fd8348163715b80bdbfbde1f", size = 2246327 }, + { url = "https://files.pythonhosted.org/packages/17/7b/cc6e9bb41bab223bd2dc70ba0b21386b85f604e27f4c3206b4205085a2ab/fonttools-4.58.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3841991c9ee2dc0562eb7f23d333d34ce81e8e27c903846f0487da21e0028eb", size = 2768901 }, + { url = "https://files.pythonhosted.org/packages/3d/15/98d75df9f2b4e7605f3260359ad6e18e027c11fa549f74fce567270ac891/fonttools-4.58.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c98f91b6a9604e7ffb5ece6ea346fa617f967c2c0944228801246ed56084664", size = 2328696 }, + { url = "https://files.pythonhosted.org/packages/a8/c8/dc92b80f5452c9c40164e01b3f78f04b835a00e673bd9355ca257008ff61/fonttools-4.58.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab9f891eb687ddf6a4e5f82901e00f992e18012ca97ab7acd15f13632acd14c1", size = 5018830 }, + { url = "https://files.pythonhosted.org/packages/19/48/8322cf177680505d6b0b6062e204f01860cb573466a88077a9b795cb70e8/fonttools-4.58.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:891c5771e8f0094b7c0dc90eda8fc75e72930b32581418f2c285a9feedfd9a68", size = 4960922 }, + { url = "https://files.pythonhosted.org/packages/14/e0/2aff149ed7eb0916de36da513d473c6fff574a7146891ce42de914899395/fonttools-4.58.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:43ba4d9646045c375d22e3473b7d82b18b31ee2ac715cd94220ffab7bc2d5c1d", size = 4997135 }, + { url = "https://files.pythonhosted.org/packages/e6/6f/4d9829b29a64a2e63a121cb11ecb1b6a9524086eef3e35470949837a1692/fonttools-4.58.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33d19f16e6d2ffd6669bda574a6589941f6c99a8d5cfb9f464038244c71555de", size = 5108701 }, + { url = "https://files.pythonhosted.org/packages/6f/1e/2d656ddd1b0cd0d222f44b2d008052c2689e66b702b9af1cd8903ddce319/fonttools-4.58.4-cp311-cp311-win32.whl", hash = "sha256:b59e5109b907da19dc9df1287454821a34a75f2632a491dd406e46ff432c2a24", size = 2200177 }, + { url = "https://files.pythonhosted.org/packages/fb/83/ba71ad053fddf4157cb0697c8da8eff6718d059f2a22986fa5f312b49c92/fonttools-4.58.4-cp311-cp311-win_amd64.whl", hash = "sha256:3d471a5b567a0d1648f2e148c9a8bcf00d9ac76eb89e976d9976582044cc2509", size = 2247892 }, + { url = "https://files.pythonhosted.org/packages/04/3c/1d1792bfe91ef46f22a3d23b4deb514c325e73c17d4f196b385b5e2faf1c/fonttools-4.58.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:462211c0f37a278494e74267a994f6be9a2023d0557aaa9ecbcbfce0f403b5a6", size = 2754082 }, + { url = "https://files.pythonhosted.org/packages/2a/1f/2b261689c901a1c3bc57a6690b0b9fc21a9a93a8b0c83aae911d3149f34e/fonttools-4.58.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c7a12fb6f769165547f00fcaa8d0df9517603ae7e04b625e5acb8639809b82d", size = 2321677 }, + { url = "https://files.pythonhosted.org/packages/fe/6b/4607add1755a1e6581ae1fc0c9a640648e0d9cdd6591cc2d581c2e07b8c3/fonttools-4.58.4-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d42c63020a922154add0a326388a60a55504629edc3274bc273cd3806b4659f", size = 4896354 }, + { url = "https://files.pythonhosted.org/packages/cd/95/34b4f483643d0cb11a1f830b72c03fdd18dbd3792d77a2eb2e130a96fada/fonttools-4.58.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f2b4e6fd45edc6805f5f2c355590b092ffc7e10a945bd6a569fc66c1d2ae7aa", size = 4941633 }, + { url = "https://files.pythonhosted.org/packages/81/ac/9bafbdb7694059c960de523e643fa5a61dd2f698f3f72c0ca18ae99257c7/fonttools-4.58.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f155b927f6efb1213a79334e4cb9904d1e18973376ffc17a0d7cd43d31981f1e", size = 4886170 }, + { url = "https://files.pythonhosted.org/packages/ae/44/a3a3b70d5709405f7525bb7cb497b4e46151e0c02e3c8a0e40e5e9fe030b/fonttools-4.58.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e38f687d5de97c7fb7da3e58169fb5ba349e464e141f83c3c2e2beb91d317816", size = 5037851 }, + { url = "https://files.pythonhosted.org/packages/21/cb/e8923d197c78969454eb876a4a55a07b59c9c4c46598f02b02411dc3b45c/fonttools-4.58.4-cp312-cp312-win32.whl", hash = "sha256:636c073b4da9db053aa683db99580cac0f7c213a953b678f69acbca3443c12cc", size = 2187428 }, + { url = "https://files.pythonhosted.org/packages/46/e6/fe50183b1a0e1018e7487ee740fa8bb127b9f5075a41e20d017201e8ab14/fonttools-4.58.4-cp312-cp312-win_amd64.whl", hash = "sha256:82e8470535743409b30913ba2822e20077acf9ea70acec40b10fcf5671dceb58", size = 2236649 }, + { url = "https://files.pythonhosted.org/packages/d4/4f/c05cab5fc1a4293e6bc535c6cb272607155a0517700f5418a4165b7f9ec8/fonttools-4.58.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5f4a64846495c543796fa59b90b7a7a9dff6839bd852741ab35a71994d685c6d", size = 2745197 }, + { url = "https://files.pythonhosted.org/packages/3e/d3/49211b1f96ae49308f4f78ca7664742377a6867f00f704cdb31b57e4b432/fonttools-4.58.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e80661793a5d4d7ad132a2aa1eae2e160fbdbb50831a0edf37c7c63b2ed36574", size = 2317272 }, + { url = "https://files.pythonhosted.org/packages/b2/11/c9972e46a6abd752a40a46960e431c795ad1f306775fc1f9e8c3081a1274/fonttools-4.58.4-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe5807fc64e4ba5130f1974c045a6e8d795f3b7fb6debfa511d1773290dbb76b", size = 4877184 }, + { url = "https://files.pythonhosted.org/packages/ea/24/5017c01c9ef8df572cc9eaf9f12be83ad8ed722ff6dc67991d3d752956e4/fonttools-4.58.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b610b9bef841cb8f4b50472494158b1e347d15cad56eac414c722eda695a6cfd", size = 4939445 }, + { url = "https://files.pythonhosted.org/packages/79/b0/538cc4d0284b5a8826b4abed93a69db52e358525d4b55c47c8cef3669767/fonttools-4.58.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2daa7f0e213c38f05f054eb5e1730bd0424aebddbeac094489ea1585807dd187", size = 4878800 }, + { url = "https://files.pythonhosted.org/packages/5a/9b/a891446b7a8250e65bffceb248508587958a94db467ffd33972723ab86c9/fonttools-4.58.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:66cccb6c0b944496b7f26450e9a66e997739c513ffaac728d24930df2fd9d35b", size = 5021259 }, + { url = "https://files.pythonhosted.org/packages/17/b2/c4d2872cff3ace3ddd1388bf15b76a1d8d5313f0a61f234e9aed287e674d/fonttools-4.58.4-cp313-cp313-win32.whl", hash = "sha256:94d2aebb5ca59a5107825520fde596e344652c1f18170ef01dacbe48fa60c889", size = 2185824 }, + { url = "https://files.pythonhosted.org/packages/98/57/cddf8bcc911d4f47dfca1956c1e3aeeb9f7c9b8e88b2a312fe8c22714e0b/fonttools-4.58.4-cp313-cp313-win_amd64.whl", hash = "sha256:b554bd6e80bba582fd326ddab296e563c20c64dca816d5e30489760e0c41529f", size = 2236382 }, + { url = "https://files.pythonhosted.org/packages/0b/2f/c536b5b9bb3c071e91d536a4d11f969e911dbb6b227939f4c5b0bca090df/fonttools-4.58.4-py3-none-any.whl", hash = "sha256:a10ce13a13f26cbb9f37512a4346bb437ad7e002ff6fa966a7ce7ff5ac3528bd", size = 1114660 }, ] [[package]] @@ -631,28 +627,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, ] -[[package]] -name = "h2" -version = "4.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "hpack" }, - { name = "hyperframe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1b/38/d7f80fd13e6582fb8e0df8c9a653dcc02b03ca34f4d72f34869298c5baf8/h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f", size = 2150682 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957 }, -] - -[[package]] -name = "hpack" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 }, -] - [[package]] name = "httpcore" version = "1.0.9" @@ -717,41 +691,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, ] -[[package]] -name = "hypercorn" -version = "0.17.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "h11" }, - { name = "h2" }, - { name = "priority" }, - { name = "taskgroup", marker = "python_full_version < '3.11'" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, - { name = "wsproto" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7e/3a/df6c27642e0dcb7aff688ca4be982f0fb5d89f2afd3096dc75347c16140f/hypercorn-0.17.3.tar.gz", hash = "sha256:1b37802ee3ac52d2d85270700d565787ab16cf19e1462ccfa9f089ca17574165", size = 44409 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/3b/dfa13a8d96aa24e40ea74a975a9906cfdc2ab2f4e3b498862a57052f04eb/hypercorn-0.17.3-py3-none-any.whl", hash = "sha256:059215dec34537f9d40a69258d323f56344805efb462959e727152b0aa504547", size = 61742 }, -] - -[[package]] -name = "hyperframe" -version = "6.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 }, -] - [[package]] name = "identify" -version = "2.6.10" +version = "2.6.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0c/83/b6ea0334e2e7327084a46aaaf71f2146fc061a192d6518c0d020120cd0aa/identify-2.6.10.tar.gz", hash = "sha256:45e92fd704f3da71cc3880036633f48b4b7265fd4de2b57627cb157216eb7eb8", size = 99201 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/88/d193a27416618628a5eea64e3223acd800b40749a96ffb322a9b55a49ed1/identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6", size = 99254 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/d3/85feeba1d097b81a44bcffa6a0beab7b4dfffe78e82fc54978d3ac380736/identify-2.6.10-py2.py3-none-any.whl", hash = "sha256:5f34248f54136beed1a7ba6a6b5c4b6cf21ff495aac7c359e1ef831ae3b8ab25", size = 99101 }, + { url = "https://files.pythonhosted.org/packages/7a/cd/18f8da995b658420625f7ef13f037be53ae04ec5ad33f9b718240dcfd48c/identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2", size = 99145 }, ] [[package]] @@ -955,54 +901,55 @@ wheels = [ [[package]] name = "matplotlib" -version = "3.10.1" +version = "3.10.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "contourpy" }, { name = "cycler" }, { name = "fonttools" }, { name = "kiwisolver" }, - { name = "numpy" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "packaging" }, { name = "pillow" }, { name = "pyparsing" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/08/b89867ecea2e305f408fbb417139a8dd941ecf7b23a2e02157c36da546f0/matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba", size = 36743335 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/b1/f70e27cf1cd76ce2a5e1aa5579d05afe3236052c6d9b9a96325bc823a17e/matplotlib-3.10.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ff2ae14910be903f4a24afdbb6d7d3a6c44da210fc7d42790b87aeac92238a16", size = 8163654 }, - { url = "https://files.pythonhosted.org/packages/26/af/5ec3d4636106718bb62503a03297125d4514f98fe818461bd9e6b9d116e4/matplotlib-3.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0721a3fd3d5756ed593220a8b86808a36c5031fce489adb5b31ee6dbb47dd5b2", size = 8037943 }, - { url = "https://files.pythonhosted.org/packages/a1/3d/07f9003a71b698b848c9925d05979ffa94a75cd25d1a587202f0bb58aa81/matplotlib-3.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0673b4b8f131890eb3a1ad058d6e065fb3c6e71f160089b65f8515373394698", size = 8449510 }, - { url = "https://files.pythonhosted.org/packages/12/87/9472d4513ff83b7cd864311821793ab72234fa201ab77310ec1b585d27e2/matplotlib-3.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e875b95ac59a7908978fe307ecdbdd9a26af7fa0f33f474a27fcf8c99f64a19", size = 8586585 }, - { url = "https://files.pythonhosted.org/packages/31/9e/fe74d237d2963adae8608faeb21f778cf246dbbf4746cef87cffbc82c4b6/matplotlib-3.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2589659ea30726284c6c91037216f64a506a9822f8e50592d48ac16a2f29e044", size = 9397911 }, - { url = "https://files.pythonhosted.org/packages/b6/1b/025d3e59e8a4281ab463162ad7d072575354a1916aba81b6a11507dfc524/matplotlib-3.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a97ff127f295817bc34517255c9db6e71de8eddaab7f837b7d341dee9f2f587f", size = 8052998 }, - { url = "https://files.pythonhosted.org/packages/a5/14/a1b840075be247bb1834b22c1e1d558740b0f618fe3a823740181ca557a1/matplotlib-3.10.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:057206ff2d6ab82ff3e94ebd94463d084760ca682ed5f150817b859372ec4401", size = 8174669 }, - { url = "https://files.pythonhosted.org/packages/0a/e4/300b08e3e08f9c98b0d5635f42edabf2f7a1d634e64cb0318a71a44ff720/matplotlib-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a144867dd6bf8ba8cb5fc81a158b645037e11b3e5cf8a50bd5f9917cb863adfe", size = 8047996 }, - { url = "https://files.pythonhosted.org/packages/75/f9/8d99ff5a2498a5f1ccf919fb46fb945109623c6108216f10f96428f388bc/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56c5d9fcd9879aa8040f196a235e2dcbdf7dd03ab5b07c0696f80bc6cf04bedd", size = 8461612 }, - { url = "https://files.pythonhosted.org/packages/40/b8/53fa08a5eaf78d3a7213fd6da1feec4bae14a81d9805e567013811ff0e85/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f69dc9713e4ad2fb21a1c30e37bd445d496524257dfda40ff4a8efb3604ab5c", size = 8602258 }, - { url = "https://files.pythonhosted.org/packages/40/87/4397d2ce808467af86684a622dd112664553e81752ea8bf61bdd89d24a41/matplotlib-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c59af3e8aca75d7744b68e8e78a669e91ccbcf1ac35d0102a7b1b46883f1dd7", size = 9408896 }, - { url = "https://files.pythonhosted.org/packages/d7/68/0d03098b3feb786cbd494df0aac15b571effda7f7cbdec267e8a8d398c16/matplotlib-3.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:11b65088c6f3dae784bc72e8d039a2580186285f87448babb9ddb2ad0082993a", size = 8061281 }, - { url = "https://files.pythonhosted.org/packages/7c/1d/5e0dc3b59c034e43de16f94deb68f4ad8a96b3ea00f4b37c160b7474928e/matplotlib-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:66e907a06e68cb6cfd652c193311d61a12b54f56809cafbed9736ce5ad92f107", size = 8175488 }, - { url = "https://files.pythonhosted.org/packages/7a/81/dae7e14042e74da658c3336ab9799128e09a1ee03964f2d89630b5d12106/matplotlib-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b4bb156abb8fa5e5b2b460196f7db7264fc6d62678c03457979e7d5254b7be", size = 8046264 }, - { url = "https://files.pythonhosted.org/packages/21/c4/22516775dcde10fc9c9571d155f90710761b028fc44f660508106c363c97/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1985ad3d97f51307a2cbfc801a930f120def19ba22864182dacef55277102ba6", size = 8452048 }, - { url = "https://files.pythonhosted.org/packages/63/23/c0615001f67ce7c96b3051d856baedc0c818a2ed84570b9bf9bde200f85d/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96f2c2f825d1257e437a1482c5a2cf4fee15db4261bd6fc0750f81ba2b4ba3d", size = 8597111 }, - { url = "https://files.pythonhosted.org/packages/ca/c0/a07939a82aed77770514348f4568177d7dadab9787ebc618a616fe3d665e/matplotlib-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35e87384ee9e488d8dd5a2dd7baf471178d38b90618d8ea147aced4ab59c9bea", size = 9402771 }, - { url = "https://files.pythonhosted.org/packages/a6/b6/a9405484fb40746fdc6ae4502b16a9d6e53282ba5baaf9ebe2da579f68c4/matplotlib-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfd414bce89cc78a7e1d25202e979b3f1af799e416010a20ab2b5ebb3a02425c", size = 8063742 }, - { url = "https://files.pythonhosted.org/packages/60/73/6770ff5e5523d00f3bc584acb6031e29ee5c8adc2336b16cd1d003675fe0/matplotlib-3.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42eee41e1b60fd83ee3292ed83a97a5f2a8239b10c26715d8a6172226988d7b", size = 8176112 }, - { url = "https://files.pythonhosted.org/packages/08/97/b0ca5da0ed54a3f6599c3ab568bdda65269bc27c21a2c97868c1625e4554/matplotlib-3.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4f0647b17b667ae745c13721602b540f7aadb2a32c5b96e924cd4fea5dcb90f1", size = 8046931 }, - { url = "https://files.pythonhosted.org/packages/df/9a/1acbdc3b165d4ce2dcd2b1a6d4ffb46a7220ceee960c922c3d50d8514067/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa3854b5f9473564ef40a41bc922be978fab217776e9ae1545c9b3a5cf2092a3", size = 8453422 }, - { url = "https://files.pythonhosted.org/packages/51/d0/2bc4368abf766203e548dc7ab57cf7e9c621f1a3c72b516cc7715347b179/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e496c01441be4c7d5f96d4e40f7fca06e20dcb40e44c8daa2e740e1757ad9e6", size = 8596819 }, - { url = "https://files.pythonhosted.org/packages/ab/1b/8b350f8a1746c37ab69dda7d7528d1fc696efb06db6ade9727b7887be16d/matplotlib-3.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d45d3f5245be5b469843450617dcad9af75ca50568acf59997bed9311131a0b", size = 9402782 }, - { url = "https://files.pythonhosted.org/packages/89/06/f570373d24d93503988ba8d04f213a372fa1ce48381c5eb15da985728498/matplotlib-3.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:8e8e25b1209161d20dfe93037c8a7f7ca796ec9aa326e6e4588d8c4a5dd1e473", size = 8063812 }, - { url = "https://files.pythonhosted.org/packages/fc/e0/8c811a925b5a7ad75135f0e5af46408b78af88bbb02a1df775100ef9bfef/matplotlib-3.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:19b06241ad89c3ae9469e07d77efa87041eac65d78df4fcf9cac318028009b01", size = 8214021 }, - { url = "https://files.pythonhosted.org/packages/4a/34/319ec2139f68ba26da9d00fce2ff9f27679fb799a6c8e7358539801fd629/matplotlib-3.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01e63101ebb3014e6e9f80d9cf9ee361a8599ddca2c3e166c563628b39305dbb", size = 8090782 }, - { url = "https://files.pythonhosted.org/packages/77/ea/9812124ab9a99df5b2eec1110e9b2edc0b8f77039abf4c56e0a376e84a29/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f06bad951eea6422ac4e8bdebcf3a70c59ea0a03338c5d2b109f57b64eb3972", size = 8478901 }, - { url = "https://files.pythonhosted.org/packages/c9/db/b05bf463689134789b06dea85828f8ebe506fa1e37593f723b65b86c9582/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfb036f34873b46978f55e240cff7a239f6c4409eac62d8145bad3fc6ba5a3", size = 8613864 }, - { url = "https://files.pythonhosted.org/packages/c2/04/41ccec4409f3023a7576df3b5c025f1a8c8b81fbfe922ecfd837ac36e081/matplotlib-3.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dc6ab14a7ab3b4d813b88ba957fc05c79493a037f54e246162033591e770de6f", size = 9409487 }, - { url = "https://files.pythonhosted.org/packages/ac/c2/0d5aae823bdcc42cc99327ecdd4d28585e15ccd5218c453b7bcd827f3421/matplotlib-3.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc411ebd5889a78dabbc457b3fa153203e22248bfa6eedc6797be5df0164dbf9", size = 8134832 }, - { url = "https://files.pythonhosted.org/packages/c8/f6/10adb696d8cbeed2ab4c2e26ecf1c80dd3847bbf3891f4a0c362e0e08a5a/matplotlib-3.10.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:648406f1899f9a818cef8c0231b44dcfc4ff36f167101c3fd1c9151f24220fdc", size = 8158685 }, - { url = "https://files.pythonhosted.org/packages/3f/84/0603d917406072763e7f9bb37747d3d74d7ecd4b943a8c947cc3ae1cf7af/matplotlib-3.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:02582304e352f40520727984a5a18f37e8187861f954fea9be7ef06569cf85b4", size = 8035491 }, - { url = "https://files.pythonhosted.org/packages/fd/7d/6a8b31dd07ed856b3eae001c9129670ef75c4698fa1c2a6ac9f00a4a7054/matplotlib-3.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3809916157ba871bcdd33d3493acd7fe3037db5daa917ca6e77975a94cef779", size = 8590087 }, +sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/ea/2bba25d289d389c7451f331ecd593944b3705f06ddf593fa7be75037d308/matplotlib-3.10.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:213fadd6348d106ca7db99e113f1bea1e65e383c3ba76e8556ba4a3054b65ae7", size = 8167862 }, + { url = "https://files.pythonhosted.org/packages/41/81/cc70b5138c926604e8c9ed810ed4c79e8116ba72e02230852f5c12c87ba2/matplotlib-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3bec61cb8221f0ca6313889308326e7bb303d0d302c5cc9e523b2f2e6c73deb", size = 8042149 }, + { url = "https://files.pythonhosted.org/packages/4a/9a/0ff45b6bfa42bb16de597e6058edf2361c298ad5ef93b327728145161bbf/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c21ae75651c0231b3ba014b6d5e08fb969c40cdb5a011e33e99ed0c9ea86ecb", size = 8453719 }, + { url = "https://files.pythonhosted.org/packages/85/c7/1866e972fed6d71ef136efbc980d4d1854ab7ef1ea8152bbd995ca231c81/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e39755580b08e30e3620efc659330eac5d6534ab7eae50fa5e31f53ee4e30", size = 8590801 }, + { url = "https://files.pythonhosted.org/packages/5d/b9/748f6626d534ab7e255bdc39dc22634d337cf3ce200f261b5d65742044a1/matplotlib-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf4636203e1190871d3a73664dea03d26fb019b66692cbfd642faafdad6208e8", size = 9402111 }, + { url = "https://files.pythonhosted.org/packages/1f/78/8bf07bd8fb67ea5665a6af188e70b57fcb2ab67057daa06b85a08e59160a/matplotlib-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:fd5641a9bb9d55f4dd2afe897a53b537c834b9012684c8444cc105895c8c16fd", size = 8057213 }, + { url = "https://files.pythonhosted.org/packages/f5/bd/af9f655456f60fe1d575f54fb14704ee299b16e999704817a7645dfce6b0/matplotlib-3.10.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0ef061f74cd488586f552d0c336b2f078d43bc00dc473d2c3e7bfee2272f3fa8", size = 8178873 }, + { url = "https://files.pythonhosted.org/packages/c2/86/e1c86690610661cd716eda5f9d0b35eaf606ae6c9b6736687cfc8f2d0cd8/matplotlib-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96985d14dc5f4a736bbea4b9de9afaa735f8a0fc2ca75be2fa9e96b2097369d", size = 8052205 }, + { url = "https://files.pythonhosted.org/packages/54/51/a9f8e49af3883dacddb2da1af5fca1f7468677f1188936452dd9aaaeb9ed/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5f0283da91e9522bdba4d6583ed9d5521566f63729ffb68334f86d0bb98049", size = 8465823 }, + { url = "https://files.pythonhosted.org/packages/e7/e3/c82963a3b86d6e6d5874cbeaa390166458a7f1961bab9feb14d3d1a10f02/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdfa07c0ec58035242bc8b2c8aae37037c9a886370eef6850703d7583e19964b", size = 8606464 }, + { url = "https://files.pythonhosted.org/packages/0e/34/24da1027e7fcdd9e82da3194c470143c551852757a4b473a09a012f5b945/matplotlib-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c0b9849a17bce080a16ebcb80a7b714b5677d0ec32161a2cc0a8e5a6030ae220", size = 9413103 }, + { url = "https://files.pythonhosted.org/packages/a6/da/948a017c3ea13fd4a97afad5fdebe2f5bbc4d28c0654510ce6fd6b06b7bd/matplotlib-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:eef6ed6c03717083bc6d69c2d7ee8624205c29a8e6ea5a31cd3492ecdbaee1e1", size = 8065492 }, + { url = "https://files.pythonhosted.org/packages/eb/43/6b80eb47d1071f234ef0c96ca370c2ca621f91c12045f1401b5c9b28a639/matplotlib-3.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ab1affc11d1f495ab9e6362b8174a25afc19c081ba5b0775ef00533a4236eea", size = 8179689 }, + { url = "https://files.pythonhosted.org/packages/0f/70/d61a591958325c357204870b5e7b164f93f2a8cca1dc6ce940f563909a13/matplotlib-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a818d8bdcafa7ed2eed74487fdb071c09c1ae24152d403952adad11fa3c65b4", size = 8050466 }, + { url = "https://files.pythonhosted.org/packages/e7/75/70c9d2306203148cc7902a961240c5927dd8728afedf35e6a77e105a2985/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748ebc3470c253e770b17d8b0557f0aa85cf8c63fd52f1a61af5b27ec0b7ffee", size = 8456252 }, + { url = "https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a", size = 8601321 }, + { url = "https://files.pythonhosted.org/packages/d2/88/d636041eb54a84b889e11872d91f7cbf036b3b0e194a70fa064eb8b04f7a/matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7", size = 9406972 }, + { url = "https://files.pythonhosted.org/packages/b1/79/0d1c165eac44405a86478082e225fce87874f7198300bbebc55faaf6d28d/matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05", size = 8067954 }, + { url = "https://files.pythonhosted.org/packages/3b/c1/23cfb566a74c696a3b338d8955c549900d18fe2b898b6e94d682ca21e7c2/matplotlib-3.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9f2efccc8dcf2b86fc4ee849eea5dcaecedd0773b30f47980dc0cbeabf26ec84", size = 8180318 }, + { url = "https://files.pythonhosted.org/packages/6c/0c/02f1c3b66b30da9ee343c343acbb6251bef5b01d34fad732446eaadcd108/matplotlib-3.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ddbba06a6c126e3301c3d272a99dcbe7f6c24c14024e80307ff03791a5f294e", size = 8051132 }, + { url = "https://files.pythonhosted.org/packages/b4/ab/8db1a5ac9b3a7352fb914133001dae889f9fcecb3146541be46bed41339c/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748302b33ae9326995b238f606e9ed840bf5886ebafcb233775d946aa8107a15", size = 8457633 }, + { url = "https://files.pythonhosted.org/packages/f5/64/41c4367bcaecbc03ef0d2a3ecee58a7065d0a36ae1aa817fe573a2da66d4/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a80fcccbef63302c0efd78042ea3c2436104c5b1a4d3ae20f864593696364ac7", size = 8601031 }, + { url = "https://files.pythonhosted.org/packages/12/6f/6cc79e9e5ab89d13ed64da28898e40fe5b105a9ab9c98f83abd24e46d7d7/matplotlib-3.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55e46cbfe1f8586adb34f7587c3e4f7dedc59d5226719faf6cb54fc24f2fd52d", size = 9406988 }, + { url = "https://files.pythonhosted.org/packages/b1/0f/eed564407bd4d935ffabf561ed31099ed609e19287409a27b6d336848653/matplotlib-3.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:151d89cb8d33cb23345cd12490c76fd5d18a56581a16d950b48c6ff19bb2ab93", size = 8068034 }, + { url = "https://files.pythonhosted.org/packages/3e/e5/2f14791ff69b12b09e9975e1d116d9578ac684460860ce542c2588cb7a1c/matplotlib-3.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c26dd9834e74d164d06433dc7be5d75a1e9890b926b3e57e74fa446e1a62c3e2", size = 8218223 }, + { url = "https://files.pythonhosted.org/packages/5c/08/30a94afd828b6e02d0a52cae4a29d6e9ccfcf4c8b56cc28b021d3588873e/matplotlib-3.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:24853dad5b8c84c8c2390fc31ce4858b6df504156893292ce8092d190ef8151d", size = 8094985 }, + { url = "https://files.pythonhosted.org/packages/89/44/f3bc6b53066c889d7a1a3ea8094c13af6a667c5ca6220ec60ecceec2dabe/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f7878214d369d7d4215e2a9075fef743be38fa401d32e6020bab2dfabaa566", size = 8483109 }, + { url = "https://files.pythonhosted.org/packages/ba/c7/473bc559beec08ebee9f86ca77a844b65747e1a6c2691e8c92e40b9f42a8/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6929fc618cb6db9cb75086f73b3219bbb25920cb24cee2ea7a12b04971a4158", size = 8618082 }, + { url = "https://files.pythonhosted.org/packages/d8/e9/6ce8edd264c8819e37bbed8172e0ccdc7107fe86999b76ab5752276357a4/matplotlib-3.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7818292a5cc372a2dc4c795e5c356942eb8350b98ef913f7fda51fe175ac5d", size = 9413699 }, + { url = "https://files.pythonhosted.org/packages/1b/92/9a45c91089c3cf690b5badd4be81e392ff086ccca8a1d4e3a08463d8a966/matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5", size = 8139044 }, + { url = "https://files.pythonhosted.org/packages/3d/d1/f54d43e95384b312ffa4a74a4326c722f3b8187aaaa12e9a84cdf3037131/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:86ab63d66bbc83fdb6733471d3bff40897c1e9921cba112accd748eee4bce5e4", size = 8162896 }, + { url = "https://files.pythonhosted.org/packages/24/a4/fbfc00c2346177c95b353dcf9b5a004106abe8730a62cb6f27e79df0a698/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a48f9c08bf7444b5d2391a83e75edb464ccda3c380384b36532a0962593a1751", size = 8039702 }, + { url = "https://files.pythonhosted.org/packages/6a/b9/59e120d24a2ec5fc2d30646adb2efb4621aab3c6d83d66fb2a7a182db032/matplotlib-3.10.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb73d8aa75a237457988f9765e4dfe1c0d2453c5ca4eabc897d4309672c8e014", size = 8594298 }, ] [[package]] @@ -1016,63 +963,59 @@ wheels = [ [[package]] name = "msgpack" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/d0/7555686ae7ff5731205df1012ede15dd9d927f6227ea151e901c7406af4f/msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e", size = 167260 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/f9/a892a6038c861fa849b11a2bb0502c07bc698ab6ea53359e5771397d883b/msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd", size = 150428 }, - { url = "https://files.pythonhosted.org/packages/df/7a/d174cc6a3b6bb85556e6a046d3193294a92f9a8e583cdbd46dc8a1d7e7f4/msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d", size = 84131 }, - { url = "https://files.pythonhosted.org/packages/08/52/bf4fbf72f897a23a56b822997a72c16de07d8d56d7bf273242f884055682/msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5", size = 81215 }, - { url = "https://files.pythonhosted.org/packages/02/95/dc0044b439b518236aaf012da4677c1b8183ce388411ad1b1e63c32d8979/msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5", size = 371229 }, - { url = "https://files.pythonhosted.org/packages/ff/75/09081792db60470bef19d9c2be89f024d366b1e1973c197bb59e6aabc647/msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e", size = 378034 }, - { url = "https://files.pythonhosted.org/packages/32/d3/c152e0c55fead87dd948d4b29879b0f14feeeec92ef1fd2ec21b107c3f49/msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b", size = 363070 }, - { url = "https://files.pythonhosted.org/packages/d9/2c/82e73506dd55f9e43ac8aa007c9dd088c6f0de2aa19e8f7330e6a65879fc/msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f", size = 359863 }, - { url = "https://files.pythonhosted.org/packages/cb/a0/3d093b248837094220e1edc9ec4337de3443b1cfeeb6e0896af8ccc4cc7a/msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68", size = 368166 }, - { url = "https://files.pythonhosted.org/packages/e4/13/7646f14f06838b406cf5a6ddbb7e8dc78b4996d891ab3b93c33d1ccc8678/msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b", size = 370105 }, - { url = "https://files.pythonhosted.org/packages/67/fa/dbbd2443e4578e165192dabbc6a22c0812cda2649261b1264ff515f19f15/msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044", size = 68513 }, - { url = "https://files.pythonhosted.org/packages/24/ce/c2c8fbf0ded750cb63cbcbb61bc1f2dfd69e16dca30a8af8ba80ec182dcd/msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f", size = 74687 }, - { url = "https://files.pythonhosted.org/packages/b7/5e/a4c7154ba65d93be91f2f1e55f90e76c5f91ccadc7efc4341e6f04c8647f/msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7", size = 150803 }, - { url = "https://files.pythonhosted.org/packages/60/c2/687684164698f1d51c41778c838d854965dd284a4b9d3a44beba9265c931/msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa", size = 84343 }, - { url = "https://files.pythonhosted.org/packages/42/ae/d3adea9bb4a1342763556078b5765e666f8fdf242e00f3f6657380920972/msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701", size = 81408 }, - { url = "https://files.pythonhosted.org/packages/dc/17/6313325a6ff40ce9c3207293aee3ba50104aed6c2c1559d20d09e5c1ff54/msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6", size = 396096 }, - { url = "https://files.pythonhosted.org/packages/a8/a1/ad7b84b91ab5a324e707f4c9761633e357820b011a01e34ce658c1dda7cc/msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59", size = 403671 }, - { url = "https://files.pythonhosted.org/packages/bb/0b/fd5b7c0b308bbf1831df0ca04ec76fe2f5bf6319833646b0a4bd5e9dc76d/msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0", size = 387414 }, - { url = "https://files.pythonhosted.org/packages/f0/03/ff8233b7c6e9929a1f5da3c7860eccd847e2523ca2de0d8ef4878d354cfa/msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e", size = 383759 }, - { url = "https://files.pythonhosted.org/packages/1f/1b/eb82e1fed5a16dddd9bc75f0854b6e2fe86c0259c4353666d7fab37d39f4/msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6", size = 394405 }, - { url = "https://files.pythonhosted.org/packages/90/2e/962c6004e373d54ecf33d695fb1402f99b51832631e37c49273cc564ffc5/msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5", size = 396041 }, - { url = "https://files.pythonhosted.org/packages/f8/20/6e03342f629474414860c48aeffcc2f7f50ddaf351d95f20c3f1c67399a8/msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88", size = 68538 }, - { url = "https://files.pythonhosted.org/packages/aa/c4/5a582fc9a87991a3e6f6800e9bb2f3c82972912235eb9539954f3e9997c7/msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788", size = 74871 }, - { url = "https://files.pythonhosted.org/packages/e1/d6/716b7ca1dbde63290d2973d22bbef1b5032ca634c3ff4384a958ec3f093a/msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d", size = 152421 }, - { url = "https://files.pythonhosted.org/packages/70/da/5312b067f6773429cec2f8f08b021c06af416bba340c912c2ec778539ed6/msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2", size = 85277 }, - { url = "https://files.pythonhosted.org/packages/28/51/da7f3ae4462e8bb98af0d5bdf2707f1b8c65a0d4f496e46b6afb06cbc286/msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420", size = 82222 }, - { url = "https://files.pythonhosted.org/packages/33/af/dc95c4b2a49cff17ce47611ca9ba218198806cad7796c0b01d1e332c86bb/msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2", size = 392971 }, - { url = "https://files.pythonhosted.org/packages/f1/54/65af8de681fa8255402c80eda2a501ba467921d5a7a028c9c22a2c2eedb5/msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39", size = 401403 }, - { url = "https://files.pythonhosted.org/packages/97/8c/e333690777bd33919ab7024269dc3c41c76ef5137b211d776fbb404bfead/msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f", size = 385356 }, - { url = "https://files.pythonhosted.org/packages/57/52/406795ba478dc1c890559dd4e89280fa86506608a28ccf3a72fbf45df9f5/msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247", size = 383028 }, - { url = "https://files.pythonhosted.org/packages/e7/69/053b6549bf90a3acadcd8232eae03e2fefc87f066a5b9fbb37e2e608859f/msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c", size = 391100 }, - { url = "https://files.pythonhosted.org/packages/23/f0/d4101d4da054f04274995ddc4086c2715d9b93111eb9ed49686c0f7ccc8a/msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b", size = 394254 }, - { url = "https://files.pythonhosted.org/packages/1c/12/cf07458f35d0d775ff3a2dc5559fa2e1fcd06c46f1ef510e594ebefdca01/msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b", size = 69085 }, - { url = "https://files.pythonhosted.org/packages/73/80/2708a4641f7d553a63bc934a3eb7214806b5b39d200133ca7f7afb0a53e8/msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f", size = 75347 }, - { url = "https://files.pythonhosted.org/packages/c8/b0/380f5f639543a4ac413e969109978feb1f3c66e931068f91ab6ab0f8be00/msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf", size = 151142 }, - { url = "https://files.pythonhosted.org/packages/c8/ee/be57e9702400a6cb2606883d55b05784fada898dfc7fd12608ab1fdb054e/msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330", size = 84523 }, - { url = "https://files.pythonhosted.org/packages/7e/3a/2919f63acca3c119565449681ad08a2f84b2171ddfcff1dba6959db2cceb/msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734", size = 81556 }, - { url = "https://files.pythonhosted.org/packages/7c/43/a11113d9e5c1498c145a8925768ea2d5fce7cbab15c99cda655aa09947ed/msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e", size = 392105 }, - { url = "https://files.pythonhosted.org/packages/2d/7b/2c1d74ca6c94f70a1add74a8393a0138172207dc5de6fc6269483519d048/msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca", size = 399979 }, - { url = "https://files.pythonhosted.org/packages/82/8c/cf64ae518c7b8efc763ca1f1348a96f0e37150061e777a8ea5430b413a74/msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915", size = 383816 }, - { url = "https://files.pythonhosted.org/packages/69/86/a847ef7a0f5ef3fa94ae20f52a4cacf596a4e4a010197fbcc27744eb9a83/msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d", size = 380973 }, - { url = "https://files.pythonhosted.org/packages/aa/90/c74cf6e1126faa93185d3b830ee97246ecc4fe12cf9d2d31318ee4246994/msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434", size = 387435 }, - { url = "https://files.pythonhosted.org/packages/7a/40/631c238f1f338eb09f4acb0f34ab5862c4e9d7eda11c1b685471a4c5ea37/msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c", size = 399082 }, - { url = "https://files.pythonhosted.org/packages/e9/1b/fa8a952be252a1555ed39f97c06778e3aeb9123aa4cccc0fd2acd0b4e315/msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc", size = 69037 }, - { url = "https://files.pythonhosted.org/packages/b6/bc/8bd826dd03e022153bfa1766dcdec4976d6c818865ed54223d71f07862b3/msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f", size = 75140 }, +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/b1/ea4f68038a18c77c9467400d166d74c4ffa536f34761f7983a104357e614/msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", size = 173555 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/52/f30da112c1dc92cf64f57d08a273ac771e7b29dea10b4b30369b2d7e8546/msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed", size = 81799 }, + { url = "https://files.pythonhosted.org/packages/e4/35/7bfc0def2f04ab4145f7f108e3563f9b4abae4ab0ed78a61f350518cc4d2/msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8", size = 78278 }, + { url = "https://files.pythonhosted.org/packages/e8/c5/df5d6c1c39856bc55f800bf82778fd4c11370667f9b9e9d51b2f5da88f20/msgpack-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2", size = 402805 }, + { url = "https://files.pythonhosted.org/packages/20/8e/0bb8c977efecfe6ea7116e2ed73a78a8d32a947f94d272586cf02a9757db/msgpack-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4", size = 408642 }, + { url = "https://files.pythonhosted.org/packages/59/a1/731d52c1aeec52006be6d1f8027c49fdc2cfc3ab7cbe7c28335b2910d7b6/msgpack-1.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0", size = 395143 }, + { url = "https://files.pythonhosted.org/packages/2b/92/b42911c52cda2ba67a6418ffa7d08969edf2e760b09015593c8a8a27a97d/msgpack-1.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26", size = 395986 }, + { url = "https://files.pythonhosted.org/packages/61/dc/8ae165337e70118d4dab651b8b562dd5066dd1e6dd57b038f32ebc3e2f07/msgpack-1.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75", size = 402682 }, + { url = "https://files.pythonhosted.org/packages/58/27/555851cb98dcbd6ce041df1eacb25ac30646575e9cd125681aa2f4b1b6f1/msgpack-1.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338", size = 406368 }, + { url = "https://files.pythonhosted.org/packages/d4/64/39a26add4ce16f24e99eabb9005e44c663db00e3fce17d4ae1ae9d61df99/msgpack-1.1.1-cp310-cp310-win32.whl", hash = "sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd", size = 65004 }, + { url = "https://files.pythonhosted.org/packages/7d/18/73dfa3e9d5d7450d39debde5b0d848139f7de23bd637a4506e36c9800fd6/msgpack-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8", size = 71548 }, + { url = "https://files.pythonhosted.org/packages/7f/83/97f24bf9848af23fe2ba04380388216defc49a8af6da0c28cc636d722502/msgpack-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558", size = 82728 }, + { url = "https://files.pythonhosted.org/packages/aa/7f/2eaa388267a78401f6e182662b08a588ef4f3de6f0eab1ec09736a7aaa2b/msgpack-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d", size = 79279 }, + { url = "https://files.pythonhosted.org/packages/f8/46/31eb60f4452c96161e4dfd26dbca562b4ec68c72e4ad07d9566d7ea35e8a/msgpack-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0", size = 423859 }, + { url = "https://files.pythonhosted.org/packages/45/16/a20fa8c32825cc7ae8457fab45670c7a8996d7746ce80ce41cc51e3b2bd7/msgpack-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f", size = 429975 }, + { url = "https://files.pythonhosted.org/packages/86/ea/6c958e07692367feeb1a1594d35e22b62f7f476f3c568b002a5ea09d443d/msgpack-1.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704", size = 413528 }, + { url = "https://files.pythonhosted.org/packages/75/05/ac84063c5dae79722bda9f68b878dc31fc3059adb8633c79f1e82c2cd946/msgpack-1.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2", size = 413338 }, + { url = "https://files.pythonhosted.org/packages/69/e8/fe86b082c781d3e1c09ca0f4dacd457ede60a13119b6ce939efe2ea77b76/msgpack-1.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2", size = 422658 }, + { url = "https://files.pythonhosted.org/packages/3b/2b/bafc9924df52d8f3bb7c00d24e57be477f4d0f967c0a31ef5e2225e035c7/msgpack-1.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752", size = 427124 }, + { url = "https://files.pythonhosted.org/packages/a2/3b/1f717e17e53e0ed0b68fa59e9188f3f610c79d7151f0e52ff3cd8eb6b2dc/msgpack-1.1.1-cp311-cp311-win32.whl", hash = "sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295", size = 65016 }, + { url = "https://files.pythonhosted.org/packages/48/45/9d1780768d3b249accecc5a38c725eb1e203d44a191f7b7ff1941f7df60c/msgpack-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458", size = 72267 }, + { url = "https://files.pythonhosted.org/packages/e3/26/389b9c593eda2b8551b2e7126ad3a06af6f9b44274eb3a4f054d48ff7e47/msgpack-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238", size = 82359 }, + { url = "https://files.pythonhosted.org/packages/ab/65/7d1de38c8a22cf8b1551469159d4b6cf49be2126adc2482de50976084d78/msgpack-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157", size = 79172 }, + { url = "https://files.pythonhosted.org/packages/0f/bd/cacf208b64d9577a62c74b677e1ada005caa9b69a05a599889d6fc2ab20a/msgpack-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce", size = 425013 }, + { url = "https://files.pythonhosted.org/packages/4d/ec/fd869e2567cc9c01278a736cfd1697941ba0d4b81a43e0aa2e8d71dab208/msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a", size = 426905 }, + { url = "https://files.pythonhosted.org/packages/55/2a/35860f33229075bce803a5593d046d8b489d7ba2fc85701e714fc1aaf898/msgpack-1.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c", size = 407336 }, + { url = "https://files.pythonhosted.org/packages/8c/16/69ed8f3ada150bf92745fb4921bd621fd2cdf5a42e25eb50bcc57a5328f0/msgpack-1.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b", size = 409485 }, + { url = "https://files.pythonhosted.org/packages/c6/b6/0c398039e4c6d0b2e37c61d7e0e9d13439f91f780686deb8ee64ecf1ae71/msgpack-1.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef", size = 412182 }, + { url = "https://files.pythonhosted.org/packages/b8/d0/0cf4a6ecb9bc960d624c93effaeaae75cbf00b3bc4a54f35c8507273cda1/msgpack-1.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a", size = 419883 }, + { url = "https://files.pythonhosted.org/packages/62/83/9697c211720fa71a2dfb632cad6196a8af3abea56eece220fde4674dc44b/msgpack-1.1.1-cp312-cp312-win32.whl", hash = "sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c", size = 65406 }, + { url = "https://files.pythonhosted.org/packages/c0/23/0abb886e80eab08f5e8c485d6f13924028602829f63b8f5fa25a06636628/msgpack-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4", size = 72558 }, + { url = "https://files.pythonhosted.org/packages/a1/38/561f01cf3577430b59b340b51329803d3a5bf6a45864a55f4ef308ac11e3/msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0", size = 81677 }, + { url = "https://files.pythonhosted.org/packages/09/48/54a89579ea36b6ae0ee001cba8c61f776451fad3c9306cd80f5b5c55be87/msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9", size = 78603 }, + { url = "https://files.pythonhosted.org/packages/a0/60/daba2699b308e95ae792cdc2ef092a38eb5ee422f9d2fbd4101526d8a210/msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8", size = 420504 }, + { url = "https://files.pythonhosted.org/packages/20/22/2ebae7ae43cd8f2debc35c631172ddf14e2a87ffcc04cf43ff9df9fff0d3/msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a", size = 423749 }, + { url = "https://files.pythonhosted.org/packages/40/1b/54c08dd5452427e1179a40b4b607e37e2664bca1c790c60c442c8e972e47/msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac", size = 404458 }, + { url = "https://files.pythonhosted.org/packages/2e/60/6bb17e9ffb080616a51f09928fdd5cac1353c9becc6c4a8abd4e57269a16/msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b", size = 405976 }, + { url = "https://files.pythonhosted.org/packages/ee/97/88983e266572e8707c1f4b99c8fd04f9eb97b43f2db40e3172d87d8642db/msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7", size = 408607 }, + { url = "https://files.pythonhosted.org/packages/bc/66/36c78af2efaffcc15a5a61ae0df53a1d025f2680122e2a9eb8442fed3ae4/msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5", size = 424172 }, + { url = "https://files.pythonhosted.org/packages/8c/87/a75eb622b555708fe0427fab96056d39d4c9892b0c784b3a721088c7ee37/msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323", size = 65347 }, + { url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341 }, ] [[package]] name = "narwhals" -version = "1.36.0" +version = "1.42.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/92/503f99e2244a271aacd6c2588e0af1b59232292b217708748cdb30214dc3/narwhals-1.36.0.tar.gz", hash = "sha256:7cd860e7e066609bd8a042bb5b8e4193275532114448210a91cbd5c622b6e5eb", size = 270385 } +sdist = { url = "https://files.pythonhosted.org/packages/df/d6/168a787b7800d6c89846b791e4f5ee6b94998a80c8c2838a019d3d71984d/narwhals-1.42.1.tar.gz", hash = "sha256:50a5635b11aeda98cf9c37e839fd34b0a24159f59a4dfae930290ad698320494", size = 492865 } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/bf/fbcbd9f8676e06ed43d644a4ddbf31478a44056487578ce67f191da430cb/narwhals-1.36.0-py3-none-any.whl", hash = "sha256:e3c50dd1d769bc145f57ae17c1f0f0da6c3d397d62cdd0bb167e9b618e95c9d6", size = 331018 }, + { url = "https://files.pythonhosted.org/packages/79/3f/8d450588206b437dd239a6d44230c63095e71135bd95d5a74347d07adbd5/narwhals-1.42.1-py3-none-any.whl", hash = "sha256:7a270d44b94ccdb277a799ae890c42e8504c537c1849f195eb14717c6184977a", size = 359888 }, ] [[package]] @@ -1086,73 +1029,147 @@ wheels = [ [[package]] name = "numpy" -version = "2.2.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/b2/ce4b867d8cd9c0ee84938ae1e6a6f7926ebf928c9090d036fc3c6a04f946/numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291", size = 20273920 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/4e/3d9e6d16237c2aa5485695f0626cbba82f6481efca2e9132368dea3b885e/numpy-2.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f4a922da1729f4c40932b2af4fe84909c7a6e167e6e99f71838ce3a29f3fe26", size = 21252117 }, - { url = "https://files.pythonhosted.org/packages/38/e4/db91349d4079cd15c02ff3b4b8882a529991d6aca077db198a2f2a670406/numpy-2.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6f91524d31b34f4a5fee24f5bc16dcd1491b668798b6d85585d836c1e633a6a", size = 14424615 }, - { url = "https://files.pythonhosted.org/packages/f8/59/6e5b011f553c37b008bd115c7ba7106a18f372588fbb1b430b7a5d2c41ce/numpy-2.2.5-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:19f4718c9012e3baea91a7dba661dcab2451cda2550678dc30d53acb91a7290f", size = 5428691 }, - { url = "https://files.pythonhosted.org/packages/a2/58/d5d70ebdac82b3a6ddf409b3749ca5786636e50fd64d60edb46442af6838/numpy-2.2.5-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:eb7fd5b184e5d277afa9ec0ad5e4eb562ecff541e7f60e69ee69c8d59e9aeaba", size = 6965010 }, - { url = "https://files.pythonhosted.org/packages/dc/a8/c290394be346d4e7b48a40baf292626fd96ec56a6398ace4c25d9079bc6a/numpy-2.2.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6413d48a9be53e183eb06495d8e3b006ef8f87c324af68241bbe7a39e8ff54c3", size = 14369885 }, - { url = "https://files.pythonhosted.org/packages/c2/70/fed13c70aabe7049368553e81d7ca40f305f305800a007a956d7cd2e5476/numpy-2.2.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7451f92eddf8503c9b8aa4fe6aa7e87fd51a29c2cfc5f7dbd72efde6c65acf57", size = 16418372 }, - { url = "https://files.pythonhosted.org/packages/04/ab/c3c14f25ddaecd6fc58a34858f6a93a21eea6c266ba162fa99f3d0de12ac/numpy-2.2.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0bcb1d057b7571334139129b7f941588f69ce7c4ed15a9d6162b2ea54ded700c", size = 15883173 }, - { url = "https://files.pythonhosted.org/packages/50/18/f53710a19042911c7aca824afe97c203728a34b8cf123e2d94621a12edc3/numpy-2.2.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36ab5b23915887543441efd0417e6a3baa08634308894316f446027611b53bf1", size = 18206881 }, - { url = "https://files.pythonhosted.org/packages/6b/ec/5b407bab82f10c65af5a5fe754728df03f960fd44d27c036b61f7b3ef255/numpy-2.2.5-cp310-cp310-win32.whl", hash = "sha256:422cc684f17bc963da5f59a31530b3936f57c95a29743056ef7a7903a5dbdf88", size = 6609852 }, - { url = "https://files.pythonhosted.org/packages/b6/f5/467ca8675c7e6c567f571d8db942cc10a87588bd9e20a909d8af4171edda/numpy-2.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:e4f0b035d9d0ed519c813ee23e0a733db81ec37d2e9503afbb6e54ccfdee0fa7", size = 12944922 }, - { url = "https://files.pythonhosted.org/packages/f5/fb/e4e4c254ba40e8f0c78218f9e86304628c75b6900509b601c8433bdb5da7/numpy-2.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c42365005c7a6c42436a54d28c43fe0e01ca11eb2ac3cefe796c25a5f98e5e9b", size = 21256475 }, - { url = "https://files.pythonhosted.org/packages/81/32/dd1f7084f5c10b2caad778258fdaeedd7fbd8afcd2510672811e6138dfac/numpy-2.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:498815b96f67dc347e03b719ef49c772589fb74b8ee9ea2c37feae915ad6ebda", size = 14461474 }, - { url = "https://files.pythonhosted.org/packages/0e/65/937cdf238ef6ac54ff749c0f66d9ee2b03646034c205cea9b6c51f2f3ad1/numpy-2.2.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6411f744f7f20081b1b4e7112e0f4c9c5b08f94b9f086e6f0adf3645f85d3a4d", size = 5426875 }, - { url = "https://files.pythonhosted.org/packages/25/17/814515fdd545b07306eaee552b65c765035ea302d17de1b9cb50852d2452/numpy-2.2.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9de6832228f617c9ef45d948ec1cd8949c482238d68b2477e6f642c33a7b0a54", size = 6969176 }, - { url = "https://files.pythonhosted.org/packages/e5/32/a66db7a5c8b5301ec329ab36d0ecca23f5e18907f43dbd593c8ec326d57c/numpy-2.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:369e0d4647c17c9363244f3468f2227d557a74b6781cb62ce57cf3ef5cc7c610", size = 14374850 }, - { url = "https://files.pythonhosted.org/packages/ad/c9/1bf6ada582eebcbe8978f5feb26584cd2b39f94ededeea034ca8f84af8c8/numpy-2.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262d23f383170f99cd9191a7c85b9a50970fe9069b2f8ab5d786eca8a675d60b", size = 16430306 }, - { url = "https://files.pythonhosted.org/packages/6a/f0/3f741863f29e128f4fcfdb99253cc971406b402b4584663710ee07f5f7eb/numpy-2.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa70fdbdc3b169d69e8c59e65c07a1c9351ceb438e627f0fdcd471015cd956be", size = 15884767 }, - { url = "https://files.pythonhosted.org/packages/98/d9/4ccd8fd6410f7bf2d312cbc98892e0e43c2fcdd1deae293aeb0a93b18071/numpy-2.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37e32e985f03c06206582a7323ef926b4e78bdaa6915095ef08070471865b906", size = 18219515 }, - { url = "https://files.pythonhosted.org/packages/b1/56/783237243d4395c6dd741cf16eeb1a9035ee3d4310900e6b17e875d1b201/numpy-2.2.5-cp311-cp311-win32.whl", hash = "sha256:f5045039100ed58fa817a6227a356240ea1b9a1bc141018864c306c1a16d4175", size = 6607842 }, - { url = "https://files.pythonhosted.org/packages/98/89/0c93baaf0094bdaaaa0536fe61a27b1dce8a505fa262a865ec142208cfe9/numpy-2.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:b13f04968b46ad705f7c8a80122a42ae8f620536ea38cf4bdd374302926424dd", size = 12949071 }, - { url = "https://files.pythonhosted.org/packages/e2/f7/1fd4ff108cd9d7ef929b8882692e23665dc9c23feecafbb9c6b80f4ec583/numpy-2.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051", size = 20948633 }, - { url = "https://files.pythonhosted.org/packages/12/03/d443c278348371b20d830af155ff2079acad6a9e60279fac2b41dbbb73d8/numpy-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc", size = 14176123 }, - { url = "https://files.pythonhosted.org/packages/2b/0b/5ca264641d0e7b14393313304da48b225d15d471250376f3fbdb1a2be603/numpy-2.2.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e", size = 5163817 }, - { url = "https://files.pythonhosted.org/packages/04/b3/d522672b9e3d28e26e1613de7675b441bbd1eaca75db95680635dd158c67/numpy-2.2.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa", size = 6698066 }, - { url = "https://files.pythonhosted.org/packages/a0/93/0f7a75c1ff02d4b76df35079676b3b2719fcdfb39abdf44c8b33f43ef37d/numpy-2.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571", size = 14087277 }, - { url = "https://files.pythonhosted.org/packages/b0/d9/7c338b923c53d431bc837b5b787052fef9ae68a56fe91e325aac0d48226e/numpy-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073", size = 16135742 }, - { url = "https://files.pythonhosted.org/packages/2d/10/4dec9184a5d74ba9867c6f7d1e9f2e0fb5fe96ff2bf50bb6f342d64f2003/numpy-2.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8", size = 15581825 }, - { url = "https://files.pythonhosted.org/packages/80/1f/2b6fcd636e848053f5b57712a7d1880b1565eec35a637fdfd0a30d5e738d/numpy-2.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae", size = 17899600 }, - { url = "https://files.pythonhosted.org/packages/ec/87/36801f4dc2623d76a0a3835975524a84bd2b18fe0f8835d45c8eae2f9ff2/numpy-2.2.5-cp312-cp312-win32.whl", hash = "sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb", size = 6312626 }, - { url = "https://files.pythonhosted.org/packages/8b/09/4ffb4d6cfe7ca6707336187951992bd8a8b9142cf345d87ab858d2d7636a/numpy-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282", size = 12645715 }, - { url = "https://files.pythonhosted.org/packages/e2/a0/0aa7f0f4509a2e07bd7a509042967c2fab635690d4f48c6c7b3afd4f448c/numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4", size = 20935102 }, - { url = "https://files.pythonhosted.org/packages/7e/e4/a6a9f4537542912ec513185396fce52cdd45bdcf3e9d921ab02a93ca5aa9/numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f", size = 14191709 }, - { url = "https://files.pythonhosted.org/packages/be/65/72f3186b6050bbfe9c43cb81f9df59ae63603491d36179cf7a7c8d216758/numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9", size = 5149173 }, - { url = "https://files.pythonhosted.org/packages/e5/e9/83e7a9432378dde5802651307ae5e9ea07bb72b416728202218cd4da2801/numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191", size = 6684502 }, - { url = "https://files.pythonhosted.org/packages/ea/27/b80da6c762394c8ee516b74c1f686fcd16c8f23b14de57ba0cad7349d1d2/numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372", size = 14084417 }, - { url = "https://files.pythonhosted.org/packages/aa/fc/ebfd32c3e124e6a1043e19c0ab0769818aa69050ce5589b63d05ff185526/numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d", size = 16133807 }, - { url = "https://files.pythonhosted.org/packages/bf/9b/4cc171a0acbe4666f7775cfd21d4eb6bb1d36d3a0431f48a73e9212d2278/numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7", size = 15575611 }, - { url = "https://files.pythonhosted.org/packages/a3/45/40f4135341850df48f8edcf949cf47b523c404b712774f8855a64c96ef29/numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73", size = 17895747 }, - { url = "https://files.pythonhosted.org/packages/f8/4c/b32a17a46f0ffbde8cc82df6d3daeaf4f552e346df143e1b188a701a8f09/numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b", size = 6309594 }, - { url = "https://files.pythonhosted.org/packages/13/ae/72e6276feb9ef06787365b05915bfdb057d01fceb4a43cb80978e518d79b/numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471", size = 12638356 }, - { url = "https://files.pythonhosted.org/packages/79/56/be8b85a9f2adb688e7ded6324e20149a03541d2b3297c3ffc1a73f46dedb/numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6", size = 20963778 }, - { url = "https://files.pythonhosted.org/packages/ff/77/19c5e62d55bff507a18c3cdff82e94fe174957bad25860a991cac719d3ab/numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba", size = 14207279 }, - { url = "https://files.pythonhosted.org/packages/75/22/aa11f22dc11ff4ffe4e849d9b63bbe8d4ac6d5fae85ddaa67dfe43be3e76/numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133", size = 5199247 }, - { url = "https://files.pythonhosted.org/packages/4f/6c/12d5e760fc62c08eded0394f62039f5a9857f758312bf01632a81d841459/numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376", size = 6711087 }, - { url = "https://files.pythonhosted.org/packages/ef/94/ece8280cf4218b2bee5cec9567629e61e51b4be501e5c6840ceb593db945/numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19", size = 14059964 }, - { url = "https://files.pythonhosted.org/packages/39/41/c5377dac0514aaeec69115830a39d905b1882819c8e65d97fc60e177e19e/numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0", size = 16121214 }, - { url = "https://files.pythonhosted.org/packages/db/54/3b9f89a943257bc8e187145c6bc0eb8e3d615655f7b14e9b490b053e8149/numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a", size = 15575788 }, - { url = "https://files.pythonhosted.org/packages/b1/c4/2e407e85df35b29f79945751b8f8e671057a13a376497d7fb2151ba0d290/numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066", size = 17893672 }, - { url = "https://files.pythonhosted.org/packages/29/7e/d0b44e129d038dba453f00d0e29ebd6eaf2f06055d72b95b9947998aca14/numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e", size = 6377102 }, - { url = "https://files.pythonhosted.org/packages/63/be/b85e4aa4bf42c6502851b971f1c326d583fcc68227385f92089cf50a7b45/numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8", size = 12750096 }, - { url = "https://files.pythonhosted.org/packages/35/e4/5ef5ef1d4308f96961198b2323bfc7c7afb0ccc0d623b01c79bc87ab496d/numpy-2.2.5-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b4ea7e1cff6784e58fe281ce7e7f05036b3e1c89c6f922a6bfbc0a7e8768adbe", size = 21083404 }, - { url = "https://files.pythonhosted.org/packages/a3/5f/bde9238e8e977652a16a4b114ed8aa8bb093d718c706eeecb5f7bfa59572/numpy-2.2.5-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d7543263084a85fbc09c704b515395398d31d6395518446237eac219eab9e55e", size = 6828578 }, - { url = "https://files.pythonhosted.org/packages/ef/7f/813f51ed86e559ab2afb6a6f33aa6baf8a560097e25e4882a938986c76c2/numpy-2.2.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0255732338c4fdd00996c0421884ea8a3651eea555c3a56b84892b66f696eb70", size = 16234796 }, - { url = "https://files.pythonhosted.org/packages/68/67/1175790323026d3337cc285cc9c50eca637d70472b5e622529df74bb8f37/numpy-2.2.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d2e3bdadaba0e040d1e7ab39db73e0afe2c74ae277f5614dad53eadbecbbb169", size = 12859001 }, +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "(python_full_version < '3.11' and platform_system == 'Darwin' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_system == 'Darwin' and sys_platform == 'win32') or (python_full_version < '3.11' and platform_system == 'Windows' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_system == 'Windows' and sys_platform == 'win32')", + "(python_full_version < '3.11' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform == 'win32')", + "(python_full_version < '3.11' and platform_system == 'Darwin' and sys_platform != 'darwin' and sys_platform != 'win32') or (python_full_version < '3.11' and platform_system == 'Windows' and sys_platform != 'darwin' and sys_platform != 'win32')", + "python_full_version < '3.11' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform != 'darwin' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245 }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048 }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542 }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301 }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320 }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050 }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034 }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185 }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149 }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620 }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963 }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743 }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616 }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579 }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005 }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570 }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548 }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521 }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866 }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455 }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348 }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362 }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103 }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382 }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462 }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618 }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511 }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783 }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506 }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190 }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828 }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006 }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765 }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736 }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719 }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072 }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213 }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632 }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532 }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885 }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467 }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144 }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217 }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014 }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935 }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122 }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143 }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260 }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225 }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374 }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391 }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754 }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476 }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666 }, +] + +[[package]] +name = "numpy" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "(python_full_version >= '3.12' and platform_system == 'Darwin' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_system == 'Darwin' and sys_platform == 'win32') or (python_full_version >= '3.12' and platform_system == 'Windows' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_system == 'Windows' and sys_platform == 'win32')", + "(python_full_version >= '3.12' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform == 'darwin') or (python_full_version >= '3.12' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform == 'win32')", + "(python_full_version == '3.11.*' and platform_system == 'Darwin' and sys_platform == 'darwin') or (python_full_version == '3.11.*' and platform_system == 'Darwin' and sys_platform == 'win32') or (python_full_version == '3.11.*' and platform_system == 'Windows' and sys_platform == 'darwin') or (python_full_version == '3.11.*' and platform_system == 'Windows' and sys_platform == 'win32')", + "(python_full_version == '3.11.*' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform == 'darwin') or (python_full_version == '3.11.*' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform == 'win32')", + "(python_full_version >= '3.12' and platform_system == 'Darwin' and sys_platform != 'darwin' and sys_platform != 'win32') or (python_full_version >= '3.12' and platform_system == 'Windows' and sys_platform != 'darwin' and sys_platform != 'win32')", + "python_full_version >= '3.12' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform != 'darwin' and sys_platform != 'win32'", + "(python_full_version == '3.11.*' and platform_system == 'Darwin' and sys_platform != 'darwin' and sys_platform != 'win32') or (python_full_version == '3.11.*' and platform_system == 'Windows' and sys_platform != 'darwin' and sys_platform != 'win32')", + "python_full_version == '3.11.*' and platform_system != 'Darwin' and platform_system != 'Windows' and sys_platform != 'darwin' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/db/8e12381333aea300890829a0a36bfa738cac95475d88982d538725143fd9/numpy-2.3.0.tar.gz", hash = "sha256:581f87f9e9e9db2cba2141400e160e9dd644ee248788d6f90636eeb8fd9260a6", size = 20382813 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/5f/df67435257d827eb3b8af66f585223dc2c3f2eb7ad0b50cb1dae2f35f494/numpy-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3c9fdde0fa18afa1099d6257eb82890ea4f3102847e692193b54e00312a9ae9", size = 21199688 }, + { url = "https://files.pythonhosted.org/packages/e5/ce/aad219575055d6c9ef29c8c540c81e1c38815d3be1fe09cdbe53d48ee838/numpy-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46d16f72c2192da7b83984aa5455baee640e33a9f1e61e656f29adf55e406c2b", size = 14359277 }, + { url = "https://files.pythonhosted.org/packages/29/6b/2d31da8e6d2ec99bed54c185337a87f8fbeccc1cd9804e38217e92f3f5e2/numpy-2.3.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a0be278be9307c4ab06b788f2a077f05e180aea817b3e41cebbd5aaf7bd85ed3", size = 5376069 }, + { url = "https://files.pythonhosted.org/packages/7d/2a/6c59a062397553ec7045c53d5fcdad44e4536e54972faa2ba44153bca984/numpy-2.3.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:99224862d1412d2562248d4710126355d3a8db7672170a39d6909ac47687a8a4", size = 6913057 }, + { url = "https://files.pythonhosted.org/packages/d5/5a/8df16f258d28d033e4f359e29d3aeb54663243ac7b71504e89deeb813202/numpy-2.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2393a914db64b0ead0ab80c962e42d09d5f385802006a6c87835acb1f58adb96", size = 14568083 }, + { url = "https://files.pythonhosted.org/packages/0a/92/0528a563dfc2cdccdcb208c0e241a4bb500d7cde218651ffb834e8febc50/numpy-2.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7729c8008d55e80784bd113787ce876ca117185c579c0d626f59b87d433ea779", size = 16929402 }, + { url = "https://files.pythonhosted.org/packages/e4/2f/e7a8c8d4a2212c527568d84f31587012cf5497a7271ea1f23332142f634e/numpy-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:06d4fb37a8d383b769281714897420c5cc3545c79dc427df57fc9b852ee0bf58", size = 15879193 }, + { url = "https://files.pythonhosted.org/packages/e2/c3/dada3f005953847fe35f42ac0fe746f6e1ea90b4c6775e4be605dcd7b578/numpy-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c39ec392b5db5088259c68250e342612db82dc80ce044cf16496cf14cf6bc6f8", size = 18665318 }, + { url = "https://files.pythonhosted.org/packages/3b/ae/3f448517dedefc8dd64d803f9d51a8904a48df730e00a3c5fb1e75a60620/numpy-2.3.0-cp311-cp311-win32.whl", hash = "sha256:ee9d3ee70d62827bc91f3ea5eee33153212c41f639918550ac0475e3588da59f", size = 6601108 }, + { url = "https://files.pythonhosted.org/packages/8c/4a/556406d2bb2b9874c8cbc840c962683ac28f21efbc9b01177d78f0199ca1/numpy-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:43c55b6a860b0eb44d42341438b03513cf3879cb3617afb749ad49307e164edd", size = 13021525 }, + { url = "https://files.pythonhosted.org/packages/ed/ee/bf54278aef30335ffa9a189f869ea09e1a195b3f4b93062164a3b02678a7/numpy-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:2e6a1409eee0cb0316cb64640a49a49ca44deb1a537e6b1121dc7c458a1299a8", size = 10170327 }, + { url = "https://files.pythonhosted.org/packages/89/59/9df493df81ac6f76e9f05cdbe013cdb0c9a37b434f6e594f5bd25e278908/numpy-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:389b85335838155a9076e9ad7f8fdba0827496ec2d2dc32ce69ce7898bde03ba", size = 20897025 }, + { url = "https://files.pythonhosted.org/packages/2f/86/4ff04335901d6cf3a6bb9c748b0097546ae5af35e455ae9b962ebff4ecd7/numpy-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9498f60cd6bb8238d8eaf468a3d5bb031d34cd12556af53510f05fcf581c1b7e", size = 14129882 }, + { url = "https://files.pythonhosted.org/packages/71/8d/a942cd4f959de7f08a79ab0c7e6cecb7431d5403dce78959a726f0f57aa1/numpy-2.3.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:622a65d40d8eb427d8e722fd410ac3ad4958002f109230bc714fa551044ebae2", size = 5110181 }, + { url = "https://files.pythonhosted.org/packages/86/5d/45850982efc7b2c839c5626fb67fbbc520d5b0d7c1ba1ae3651f2f74c296/numpy-2.3.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b9446d9d8505aadadb686d51d838f2b6688c9e85636a0c3abaeb55ed54756459", size = 6647581 }, + { url = "https://files.pythonhosted.org/packages/1a/c0/c871d4a83f93b00373d3eebe4b01525eee8ef10b623a335ec262b58f4dc1/numpy-2.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:50080245365d75137a2bf46151e975de63146ae6d79f7e6bd5c0e85c9931d06a", size = 14262317 }, + { url = "https://files.pythonhosted.org/packages/b7/f6/bc47f5fa666d5ff4145254f9e618d56e6a4ef9b874654ca74c19113bb538/numpy-2.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c24bb4113c66936eeaa0dc1e47c74770453d34f46ee07ae4efd853a2ed1ad10a", size = 16633919 }, + { url = "https://files.pythonhosted.org/packages/f5/b4/65f48009ca0c9b76df5f404fccdea5a985a1bb2e34e97f21a17d9ad1a4ba/numpy-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4d8d294287fdf685281e671886c6dcdf0291a7c19db3e5cb4178d07ccf6ecc67", size = 15567651 }, + { url = "https://files.pythonhosted.org/packages/f1/62/5367855a2018578e9334ed08252ef67cc302e53edc869666f71641cad40b/numpy-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6295f81f093b7f5769d1728a6bd8bf7466de2adfa771ede944ce6711382b89dc", size = 18361723 }, + { url = "https://files.pythonhosted.org/packages/d4/75/5baed8cd867eabee8aad1e74d7197d73971d6a3d40c821f1848b8fab8b84/numpy-2.3.0-cp312-cp312-win32.whl", hash = "sha256:e6648078bdd974ef5d15cecc31b0c410e2e24178a6e10bf511e0557eed0f2570", size = 6318285 }, + { url = "https://files.pythonhosted.org/packages/bc/49/d5781eaa1a15acb3b3a3f49dc9e2ff18d92d0ce5c2976f4ab5c0a7360250/numpy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:0898c67a58cdaaf29994bc0e2c65230fd4de0ac40afaf1584ed0b02cd74c6fdd", size = 12732594 }, + { url = "https://files.pythonhosted.org/packages/c2/1c/6d343e030815c7c97a1f9fbad00211b47717c7fe446834c224bd5311e6f1/numpy-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:bd8df082b6c4695753ad6193018c05aac465d634834dca47a3ae06d4bb22d9ea", size = 9891498 }, + { url = "https://files.pythonhosted.org/packages/73/fc/1d67f751fd4dbafc5780244fe699bc4084268bad44b7c5deb0492473127b/numpy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5754ab5595bfa2c2387d241296e0381c21f44a4b90a776c3c1d39eede13a746a", size = 20889633 }, + { url = "https://files.pythonhosted.org/packages/e8/95/73ffdb69e5c3f19ec4530f8924c4386e7ba097efc94b9c0aff607178ad94/numpy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d11fa02f77752d8099573d64e5fe33de3229b6632036ec08f7080f46b6649959", size = 14151683 }, + { url = "https://files.pythonhosted.org/packages/64/d5/06d4bb31bb65a1d9c419eb5676173a2f90fd8da3c59f816cc54c640ce265/numpy-2.3.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:aba48d17e87688a765ab1cd557882052f238e2f36545dfa8e29e6a91aef77afe", size = 5102683 }, + { url = "https://files.pythonhosted.org/packages/12/8b/6c2cef44f8ccdc231f6b56013dff1d71138c48124334aded36b1a1b30c5a/numpy-2.3.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4dc58865623023b63b10d52f18abaac3729346a7a46a778381e0e3af4b7f3beb", size = 6640253 }, + { url = "https://files.pythonhosted.org/packages/62/aa/fca4bf8de3396ddb59544df9b75ffe5b73096174de97a9492d426f5cd4aa/numpy-2.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:df470d376f54e052c76517393fa443758fefcdd634645bc9c1f84eafc67087f0", size = 14258658 }, + { url = "https://files.pythonhosted.org/packages/1c/12/734dce1087eed1875f2297f687e671cfe53a091b6f2f55f0c7241aad041b/numpy-2.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:87717eb24d4a8a64683b7a4e91ace04e2f5c7c77872f823f02a94feee186168f", size = 16628765 }, + { url = "https://files.pythonhosted.org/packages/48/03/ffa41ade0e825cbcd5606a5669962419528212a16082763fc051a7247d76/numpy-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fa264d56882b59dcb5ea4d6ab6f31d0c58a57b41aec605848b6eb2ef4a43e8", size = 15564335 }, + { url = "https://files.pythonhosted.org/packages/07/58/869398a11863310aee0ff85a3e13b4c12f20d032b90c4b3ee93c3b728393/numpy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e651756066a0eaf900916497e20e02fe1ae544187cb0fe88de981671ee7f6270", size = 18360608 }, + { url = "https://files.pythonhosted.org/packages/2f/8a/5756935752ad278c17e8a061eb2127c9a3edf4ba2c31779548b336f23c8d/numpy-2.3.0-cp313-cp313-win32.whl", hash = "sha256:e43c3cce3b6ae5f94696669ff2a6eafd9a6b9332008bafa4117af70f4b88be6f", size = 6310005 }, + { url = "https://files.pythonhosted.org/packages/08/60/61d60cf0dfc0bf15381eaef46366ebc0c1a787856d1db0c80b006092af84/numpy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:81ae0bf2564cf475f94be4a27ef7bcf8af0c3e28da46770fc904da9abd5279b5", size = 12729093 }, + { url = "https://files.pythonhosted.org/packages/66/31/2f2f2d2b3e3c32d5753d01437240feaa32220b73258c9eef2e42a0832866/numpy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:c8738baa52505fa6e82778580b23f945e3578412554d937093eac9205e845e6e", size = 9885689 }, + { url = "https://files.pythonhosted.org/packages/f1/89/c7828f23cc50f607ceb912774bb4cff225ccae7131c431398ad8400e2c98/numpy-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:39b27d8b38942a647f048b675f134dd5a567f95bfff481f9109ec308515c51d8", size = 20986612 }, + { url = "https://files.pythonhosted.org/packages/dd/46/79ecf47da34c4c50eedec7511e53d57ffdfd31c742c00be7dc1d5ffdb917/numpy-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0eba4a1ea88f9a6f30f56fdafdeb8da3774349eacddab9581a21234b8535d3d3", size = 14298953 }, + { url = "https://files.pythonhosted.org/packages/59/44/f6caf50713d6ff4480640bccb2a534ce1d8e6e0960c8f864947439f0ee95/numpy-2.3.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0f1f11d0a1da54927436505a5a7670b154eac27f5672afc389661013dfe3d4f", size = 5225806 }, + { url = "https://files.pythonhosted.org/packages/a6/43/e1fd1aca7c97e234dd05e66de4ab7a5be54548257efcdd1bc33637e72102/numpy-2.3.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:690d0a5b60a47e1f9dcec7b77750a4854c0d690e9058b7bef3106e3ae9117808", size = 6735169 }, + { url = "https://files.pythonhosted.org/packages/84/89/f76f93b06a03177c0faa7ca94d0856c4e5c4bcaf3c5f77640c9ed0303e1c/numpy-2.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8b51ead2b258284458e570942137155978583e407babc22e3d0ed7af33ce06f8", size = 14330701 }, + { url = "https://files.pythonhosted.org/packages/aa/f5/4858c3e9ff7a7d64561b20580cf7cc5d085794bd465a19604945d6501f6c/numpy-2.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:aaf81c7b82c73bd9b45e79cfb9476cb9c29e937494bfe9092c26aece812818ad", size = 16692983 }, + { url = "https://files.pythonhosted.org/packages/08/17/0e3b4182e691a10e9483bcc62b4bb8693dbf9ea5dc9ba0b77a60435074bb/numpy-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f420033a20b4f6a2a11f585f93c843ac40686a7c3fa514060a97d9de93e5e72b", size = 15641435 }, + { url = "https://files.pythonhosted.org/packages/4e/d5/463279fda028d3c1efa74e7e8d507605ae87f33dbd0543cf4c4527c8b882/numpy-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d344ca32ab482bcf8735d8f95091ad081f97120546f3d250240868430ce52555", size = 18433798 }, + { url = "https://files.pythonhosted.org/packages/0e/1e/7a9d98c886d4c39a2b4d3a7c026bffcf8fbcaf518782132d12a301cfc47a/numpy-2.3.0-cp313-cp313t-win32.whl", hash = "sha256:48a2e8eaf76364c32a1feaa60d6925eaf32ed7a040183b807e02674305beef61", size = 6438632 }, + { url = "https://files.pythonhosted.org/packages/fe/ab/66fc909931d5eb230107d016861824f335ae2c0533f422e654e5ff556784/numpy-2.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ba17f93a94e503551f154de210e4d50c5e3ee20f7e7a1b5f6ce3f22d419b93bb", size = 12868491 }, + { url = "https://files.pythonhosted.org/packages/ee/e8/2c8a1c9e34d6f6d600c83d5ce5b71646c32a13f34ca5c518cc060639841c/numpy-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f14e016d9409680959691c109be98c436c6249eaf7f118b424679793607b5944", size = 9935345 }, + { url = "https://files.pythonhosted.org/packages/6a/a2/f8c1133f90eaa1c11bbbec1dc28a42054d0ce74bc2c9838c5437ba5d4980/numpy-2.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80b46117c7359de8167cc00a2c7d823bdd505e8c7727ae0871025a86d668283b", size = 21070759 }, + { url = "https://files.pythonhosted.org/packages/6c/e0/4c05fc44ba28463096eee5ae2a12832c8d2759cc5bcec34ae33386d3ff83/numpy-2.3.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:5814a0f43e70c061f47abd5857d120179609ddc32a613138cbb6c4e9e2dbdda5", size = 5301054 }, + { url = "https://files.pythonhosted.org/packages/8a/3b/6c06cdebe922bbc2a466fe2105f50f661238ea223972a69c7deb823821e7/numpy-2.3.0-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ef6c1e88fd6b81ac6d215ed71dc8cd027e54d4bf1d2682d362449097156267a2", size = 6817520 }, + { url = "https://files.pythonhosted.org/packages/9d/a3/1e536797fd10eb3c5dbd2e376671667c9af19e241843548575267242ea02/numpy-2.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33a5a12a45bb82d9997e2c0b12adae97507ad7c347546190a18ff14c28bbca12", size = 14398078 }, + { url = "https://files.pythonhosted.org/packages/7c/61/9d574b10d9368ecb1a0c923952aa593510a20df4940aa615b3a71337c8db/numpy-2.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:54dfc8681c1906d239e95ab1508d0a533c4a9505e52ee2d71a5472b04437ef97", size = 16751324 }, + { url = "https://files.pythonhosted.org/packages/39/de/bcad52ce972dc26232629ca3a99721fd4b22c1d2bda84d5db6541913ef9c/numpy-2.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e017a8a251ff4d18d71f139e28bdc7c31edba7a507f72b1414ed902cbe48c74d", size = 12924237 }, ] [[package]] name = "oauthlib" -version = "3.2.2" +version = "3.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/fa/fbf4001037904031639e6bfbfc02badfc7e12f137a8afa254df6c4c8a670/oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918", size = 177352 } +sdist = { url = "https://files.pythonhosted.org/packages/98/8a/6ea75ff7acf89f43afb157604429af4661a9840b1f2cece602b6a13c1893/oauthlib-3.3.0.tar.gz", hash = "sha256:4e707cf88d7dfc22a8cce22ca736a2eef9967c1dd3845efc0703fc922353eeb2", size = 190292 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/80/cab10959dc1faead58dc8384a781dfbf93cb4d33d50988f7a69f1b7c9bbe/oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", size = 151688 }, + { url = "https://files.pythonhosted.org/packages/e1/3d/760b1456010ed11ce87c0109007f0166078dfdada7597f0091ae76eb7305/oauthlib-3.3.0-py3-none-any.whl", hash = "sha256:a2b3a0a2a4ec2feb4b9110f56674a39b2cc2f23e14713f4ed20441dfba14e934", size = 165155 }, ] [[package]] @@ -1166,50 +1183,51 @@ wheels = [ [[package]] name = "pandas" -version = "2.2.3" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "python-dateutil" }, { name = "pytz" }, { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/70/c853aec59839bceed032d52010ff5f1b8d87dc3114b762e4ba2727661a3b/pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5", size = 12580827 }, - { url = "https://files.pythonhosted.org/packages/99/f2/c4527768739ffa4469b2b4fff05aa3768a478aed89a2f271a79a40eee984/pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348", size = 11303897 }, - { url = "https://files.pythonhosted.org/packages/ed/12/86c1747ea27989d7a4064f806ce2bae2c6d575b950be087837bdfcabacc9/pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed", size = 66480908 }, - { url = "https://files.pythonhosted.org/packages/44/50/7db2cd5e6373ae796f0ddad3675268c8d59fb6076e66f0c339d61cea886b/pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57", size = 13064210 }, - { url = "https://files.pythonhosted.org/packages/61/61/a89015a6d5536cb0d6c3ba02cebed51a95538cf83472975275e28ebf7d0c/pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42", size = 16754292 }, - { url = "https://files.pythonhosted.org/packages/ce/0d/4cc7b69ce37fac07645a94e1d4b0880b15999494372c1523508511b09e40/pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f", size = 14416379 }, - { url = "https://files.pythonhosted.org/packages/31/9e/6ebb433de864a6cd45716af52a4d7a8c3c9aaf3a98368e61db9e69e69a9c/pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645", size = 11598471 }, - { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222 }, - { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274 }, - { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836 }, - { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505 }, - { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420 }, - { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457 }, - { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166 }, - { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 }, - { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 }, - { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 }, - { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 }, - { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 }, - { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 }, - { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 }, - { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 }, - { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 }, - { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 }, - { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 }, - { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 }, - { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 }, - { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 }, - { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 }, - { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 }, - { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 }, - { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 }, - { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 }, - { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, +sdist = { url = "https://files.pythonhosted.org/packages/72/51/48f713c4c728d7c55ef7444ba5ea027c26998d96d1a40953b346438602fc/pandas-2.3.0.tar.gz", hash = "sha256:34600ab34ebf1131a7613a260a61dbe8b62c188ec0ea4c296da7c9a06b004133", size = 4484490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/2d/df6b98c736ba51b8eaa71229e8fcd91233a831ec00ab520e1e23090cc072/pandas-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:625466edd01d43b75b1883a64d859168e4556261a5035b32f9d743b67ef44634", size = 11527531 }, + { url = "https://files.pythonhosted.org/packages/77/1c/3f8c331d223f86ba1d0ed7d3ed7fcf1501c6f250882489cc820d2567ddbf/pandas-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6872d695c896f00df46b71648eea332279ef4077a409e2fe94220208b6bb675", size = 10774764 }, + { url = "https://files.pythonhosted.org/packages/1b/45/d2599400fad7fe06b849bd40b52c65684bc88fbe5f0a474d0513d057a377/pandas-2.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4dd97c19bd06bc557ad787a15b6489d2614ddaab5d104a0310eb314c724b2d2", size = 11711963 }, + { url = "https://files.pythonhosted.org/packages/66/f8/5508bc45e994e698dbc93607ee6b9b6eb67df978dc10ee2b09df80103d9e/pandas-2.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:034abd6f3db8b9880aaee98f4f5d4dbec7c4829938463ec046517220b2f8574e", size = 12349446 }, + { url = "https://files.pythonhosted.org/packages/f7/fc/17851e1b1ea0c8456ba90a2f514c35134dd56d981cf30ccdc501a0adeac4/pandas-2.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23c2b2dc5213810208ca0b80b8666670eb4660bbfd9d45f58592cc4ddcfd62e1", size = 12920002 }, + { url = "https://files.pythonhosted.org/packages/a1/9b/8743be105989c81fa33f8e2a4e9822ac0ad4aaf812c00fee6bb09fc814f9/pandas-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:39ff73ec07be5e90330cc6ff5705c651ace83374189dcdcb46e6ff54b4a72cd6", size = 13651218 }, + { url = "https://files.pythonhosted.org/packages/26/fa/8eeb2353f6d40974a6a9fd4081ad1700e2386cf4264a8f28542fd10b3e38/pandas-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:40cecc4ea5abd2921682b57532baea5588cc5f80f0231c624056b146887274d2", size = 11082485 }, + { url = "https://files.pythonhosted.org/packages/96/1e/ba313812a699fe37bf62e6194265a4621be11833f5fce46d9eae22acb5d7/pandas-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8adff9f138fc614347ff33812046787f7d43b3cef7c0f0171b3340cae333f6ca", size = 11551836 }, + { url = "https://files.pythonhosted.org/packages/1b/cc/0af9c07f8d714ea563b12383a7e5bde9479cf32413ee2f346a9c5a801f22/pandas-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e5f08eb9a445d07720776df6e641975665c9ea12c9d8a331e0f6890f2dcd76ef", size = 10807977 }, + { url = "https://files.pythonhosted.org/packages/ee/3e/8c0fb7e2cf4a55198466ced1ca6a9054ae3b7e7630df7757031df10001fd/pandas-2.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa35c266c8cd1a67d75971a1912b185b492d257092bdd2709bbdebe574ed228d", size = 11788230 }, + { url = "https://files.pythonhosted.org/packages/14/22/b493ec614582307faf3f94989be0f7f0a71932ed6f56c9a80c0bb4a3b51e/pandas-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a0cc77b0f089d2d2ffe3007db58f170dae9b9f54e569b299db871a3ab5bf46", size = 12370423 }, + { url = "https://files.pythonhosted.org/packages/9f/74/b012addb34cda5ce855218a37b258c4e056a0b9b334d116e518d72638737/pandas-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c06f6f144ad0a1bf84699aeea7eff6068ca5c63ceb404798198af7eb86082e33", size = 12990594 }, + { url = "https://files.pythonhosted.org/packages/95/81/b310e60d033ab64b08e66c635b94076488f0b6ce6a674379dd5b224fc51c/pandas-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed16339bc354a73e0a609df36d256672c7d296f3f767ac07257801aa064ff73c", size = 13745952 }, + { url = "https://files.pythonhosted.org/packages/25/ac/f6ee5250a8881b55bd3aecde9b8cfddea2f2b43e3588bca68a4e9aaf46c8/pandas-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:fa07e138b3f6c04addfeaf56cc7fdb96c3b68a3fe5e5401251f231fce40a0d7a", size = 11094534 }, + { url = "https://files.pythonhosted.org/packages/94/46/24192607058dd607dbfacdd060a2370f6afb19c2ccb617406469b9aeb8e7/pandas-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2eb4728a18dcd2908c7fccf74a982e241b467d178724545a48d0caf534b38ebf", size = 11573865 }, + { url = "https://files.pythonhosted.org/packages/9f/cc/ae8ea3b800757a70c9fdccc68b67dc0280a6e814efcf74e4211fd5dea1ca/pandas-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9d8c3187be7479ea5c3d30c32a5d73d62a621166675063b2edd21bc47614027", size = 10702154 }, + { url = "https://files.pythonhosted.org/packages/d8/ba/a7883d7aab3d24c6540a2768f679e7414582cc389876d469b40ec749d78b/pandas-2.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ff730713d4c4f2f1c860e36c005c7cefc1c7c80c21c0688fd605aa43c9fcf09", size = 11262180 }, + { url = "https://files.pythonhosted.org/packages/01/a5/931fc3ad333d9d87b10107d948d757d67ebcfc33b1988d5faccc39c6845c/pandas-2.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba24af48643b12ffe49b27065d3babd52702d95ab70f50e1b34f71ca703e2c0d", size = 11991493 }, + { url = "https://files.pythonhosted.org/packages/d7/bf/0213986830a92d44d55153c1d69b509431a972eb73f204242988c4e66e86/pandas-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:404d681c698e3c8a40a61d0cd9412cc7364ab9a9cc6e144ae2992e11a2e77a20", size = 12470733 }, + { url = "https://files.pythonhosted.org/packages/a4/0e/21eb48a3a34a7d4bac982afc2c4eb5ab09f2d988bdf29d92ba9ae8e90a79/pandas-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6021910b086b3ca756755e86ddc64e0ddafd5e58e076c72cb1585162e5ad259b", size = 13212406 }, + { url = "https://files.pythonhosted.org/packages/1f/d9/74017c4eec7a28892d8d6e31ae9de3baef71f5a5286e74e6b7aad7f8c837/pandas-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:094e271a15b579650ebf4c5155c05dcd2a14fd4fdd72cf4854b2f7ad31ea30be", size = 10976199 }, + { url = "https://files.pythonhosted.org/packages/d3/57/5cb75a56a4842bbd0511c3d1c79186d8315b82dac802118322b2de1194fe/pandas-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c7e2fc25f89a49a11599ec1e76821322439d90820108309bf42130d2f36c983", size = 11518913 }, + { url = "https://files.pythonhosted.org/packages/05/01/0c8785610e465e4948a01a059562176e4c8088aa257e2e074db868f86d4e/pandas-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6da97aeb6a6d233fb6b17986234cc723b396b50a3c6804776351994f2a658fd", size = 10655249 }, + { url = "https://files.pythonhosted.org/packages/e8/6a/47fd7517cd8abe72a58706aab2b99e9438360d36dcdb052cf917b7bf3bdc/pandas-2.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb32dc743b52467d488e7a7c8039b821da2826a9ba4f85b89ea95274f863280f", size = 11328359 }, + { url = "https://files.pythonhosted.org/packages/2a/b3/463bfe819ed60fb7e7ddffb4ae2ee04b887b3444feee6c19437b8f834837/pandas-2.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:213cd63c43263dbb522c1f8a7c9d072e25900f6975596f883f4bebd77295d4f3", size = 12024789 }, + { url = "https://files.pythonhosted.org/packages/04/0c/e0704ccdb0ac40aeb3434d1c641c43d05f75c92e67525df39575ace35468/pandas-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1d2b33e68d0ce64e26a4acc2e72d747292084f4e8db4c847c6f5f6cbe56ed6d8", size = 12480734 }, + { url = "https://files.pythonhosted.org/packages/e9/df/815d6583967001153bb27f5cf075653d69d51ad887ebbf4cfe1173a1ac58/pandas-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:430a63bae10b5086995db1b02694996336e5a8ac9a96b4200572b413dfdfccb9", size = 13223381 }, + { url = "https://files.pythonhosted.org/packages/79/88/ca5973ed07b7f484c493e941dbff990861ca55291ff7ac67c815ce347395/pandas-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4930255e28ff5545e2ca404637bcc56f031893142773b3468dc021c6c32a1390", size = 10970135 }, + { url = "https://files.pythonhosted.org/packages/24/fb/0994c14d1f7909ce83f0b1fb27958135513c4f3f2528bde216180aa73bfc/pandas-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f925f1ef673b4bd0271b1809b72b3270384f2b7d9d14a189b12b7fc02574d575", size = 12141356 }, + { url = "https://files.pythonhosted.org/packages/9d/a2/9b903e5962134497ac4f8a96f862ee3081cb2506f69f8e4778ce3d9c9d82/pandas-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78ad363ddb873a631e92a3c063ade1ecfb34cae71e9a2be6ad100f875ac1042", size = 11474674 }, + { url = "https://files.pythonhosted.org/packages/81/3a/3806d041bce032f8de44380f866059437fb79e36d6b22c82c187e65f765b/pandas-2.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951805d146922aed8357e4cc5671b8b0b9be1027f0619cea132a9f3f65f2f09c", size = 11439876 }, + { url = "https://files.pythonhosted.org/packages/15/aa/3fc3181d12b95da71f5c2537c3e3b3af6ab3a8c392ab41ebb766e0929bc6/pandas-2.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a881bc1309f3fce34696d07b00f13335c41f5f5a8770a33b09ebe23261cfc67", size = 11966182 }, + { url = "https://files.pythonhosted.org/packages/37/e7/e12f2d9b0a2c4a2cc86e2aabff7ccfd24f03e597d770abfa2acd313ee46b/pandas-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e1991bbb96f4050b09b5f811253c4f3cf05ee89a589379aa36cd623f21a31d6f", size = 12547686 }, + { url = "https://files.pythonhosted.org/packages/39/c2/646d2e93e0af70f4e5359d870a63584dacbc324b54d73e6b3267920ff117/pandas-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bb3be958022198531eb7ec2008cfc78c5b1eed51af8600c6c5d9160d89d8d249", size = 13231847 }, ] [[package]] @@ -1300,33 +1318,33 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.3.7" +version = "4.3.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291 } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 }, + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, ] [[package]] name = "plotly" -version = "6.0.1" +version = "6.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "narwhals" }, { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/cc/e41b5f697ae403f0b50e47b7af2e36642a193085f553bf7cc1169362873a/plotly-6.0.1.tar.gz", hash = "sha256:dd8400229872b6e3c964b099be699f8d00c489a974f2cfccfad5e8240873366b", size = 8094643 } +sdist = { url = "https://files.pythonhosted.org/packages/ae/77/431447616eda6a432dc3ce541b3f808ecb8803ea3d4ab2573b67f8eb4208/plotly-6.1.2.tar.gz", hash = "sha256:4fdaa228926ba3e3a213f4d1713287e69dcad1a7e66cf2025bd7d7026d5014b4", size = 7662971 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/65/ad2bc85f7377f5cfba5d4466d5474423a3fb7f6a97fd807c06f92dd3e721/plotly-6.0.1-py3-none-any.whl", hash = "sha256:4714db20fea57a435692c548a4eb4fae454f7daddf15f8d8ba7e1045681d7768", size = 14805757 }, + { url = "https://files.pythonhosted.org/packages/bf/6f/759d5da0517547a5d38aabf05d04d9f8adf83391d2c7fc33f904417d3ba2/plotly-6.1.2-py3-none-any.whl", hash = "sha256:f1548a8ed9158d59e03d7fed548c7db5549f3130d9ae19293c8638c202648f6d", size = 16265530 }, ] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, ] [[package]] @@ -1345,15 +1363,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707 }, ] -[[package]] -name = "priority" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/3c/eb7c35f4dcede96fca1842dac5f4f5d15511aa4b52f3a961219e68ae9204/priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0", size = 24792 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/5f/82c8074f7e84978129347c2c6ec8b6c59f3584ff1a20bc3c940a3e061790/priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa", size = 8946 }, -] - [[package]] name = "pycparser" version = "2.22" @@ -1365,7 +1374,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.11.3" +version = "2.11.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -1373,96 +1382,96 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513 } +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591 }, + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782 }, ] [[package]] name = "pydantic-core" -version = "2.33.1" +version = "2.33.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/ea/5f572806ab4d4223d11551af814d243b0e3e02cc6913def4d1fe4a5ca41c/pydantic_core-2.33.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3077cfdb6125cc8dab61b155fdd714663e401f0e6883f9632118ec12cf42df26", size = 2044021 }, - { url = "https://files.pythonhosted.org/packages/8c/d1/f86cc96d2aa80e3881140d16d12ef2b491223f90b28b9a911346c04ac359/pydantic_core-2.33.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ffab8b2908d152e74862d276cf5017c81a2f3719f14e8e3e8d6b83fda863927", size = 1861742 }, - { url = "https://files.pythonhosted.org/packages/37/08/fbd2cd1e9fc735a0df0142fac41c114ad9602d1c004aea340169ae90973b/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5183e4f6a2d468787243ebcd70cf4098c247e60d73fb7d68d5bc1e1beaa0c4db", size = 1910414 }, - { url = "https://files.pythonhosted.org/packages/7f/73/3ac217751decbf8d6cb9443cec9b9eb0130eeada6ae56403e11b486e277e/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:398a38d323f37714023be1e0285765f0a27243a8b1506b7b7de87b647b517e48", size = 1996848 }, - { url = "https://files.pythonhosted.org/packages/9a/f5/5c26b265cdcff2661e2520d2d1e9db72d117ea00eb41e00a76efe68cb009/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87d3776f0001b43acebfa86f8c64019c043b55cc5a6a2e313d728b5c95b46969", size = 2141055 }, - { url = "https://files.pythonhosted.org/packages/5d/14/a9c3cee817ef2f8347c5ce0713e91867a0dceceefcb2973942855c917379/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c566dd9c5f63d22226409553531f89de0cac55397f2ab8d97d6f06cfce6d947e", size = 2753806 }, - { url = "https://files.pythonhosted.org/packages/f2/68/866ce83a51dd37e7c604ce0050ff6ad26de65a7799df89f4db87dd93d1d6/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d5f3acc81452c56895e90643a625302bd6be351e7010664151cc55b7b97f89", size = 2007777 }, - { url = "https://files.pythonhosted.org/packages/b6/a8/36771f4404bb3e49bd6d4344da4dede0bf89cc1e01f3b723c47248a3761c/pydantic_core-2.33.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3a07fadec2a13274a8d861d3d37c61e97a816beae717efccaa4b36dfcaadcde", size = 2122803 }, - { url = "https://files.pythonhosted.org/packages/18/9c/730a09b2694aa89360d20756369822d98dc2f31b717c21df33b64ffd1f50/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f99aeda58dce827f76963ee87a0ebe75e648c72ff9ba1174a253f6744f518f65", size = 2086755 }, - { url = "https://files.pythonhosted.org/packages/54/8e/2dccd89602b5ec31d1c58138d02340ecb2ebb8c2cac3cc66b65ce3edb6ce/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:902dbc832141aa0ec374f4310f1e4e7febeebc3256f00dc359a9ac3f264a45dc", size = 2257358 }, - { url = "https://files.pythonhosted.org/packages/d1/9c/126e4ac1bfad8a95a9837acdd0963695d69264179ba4ede8b8c40d741702/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fe44d56aa0b00d66640aa84a3cbe80b7a3ccdc6f0b1ca71090696a6d4777c091", size = 2257916 }, - { url = "https://files.pythonhosted.org/packages/7d/ba/91eea2047e681a6853c81c20aeca9dcdaa5402ccb7404a2097c2adf9d038/pydantic_core-2.33.1-cp310-cp310-win32.whl", hash = "sha256:ed3eb16d51257c763539bde21e011092f127a2202692afaeaccb50db55a31383", size = 1923823 }, - { url = "https://files.pythonhosted.org/packages/94/c0/fcdf739bf60d836a38811476f6ecd50374880b01e3014318b6e809ddfd52/pydantic_core-2.33.1-cp310-cp310-win_amd64.whl", hash = "sha256:694ad99a7f6718c1a498dc170ca430687a39894a60327f548e02a9c7ee4b6504", size = 1952494 }, - { url = "https://files.pythonhosted.org/packages/d6/7f/c6298830cb780c46b4f46bb24298d01019ffa4d21769f39b908cd14bbd50/pydantic_core-2.33.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e966fc3caaf9f1d96b349b0341c70c8d6573bf1bac7261f7b0ba88f96c56c24", size = 2044224 }, - { url = "https://files.pythonhosted.org/packages/a8/65/6ab3a536776cad5343f625245bd38165d6663256ad43f3a200e5936afd6c/pydantic_core-2.33.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bfd0adeee563d59c598ceabddf2c92eec77abcb3f4a391b19aa7366170bd9e30", size = 1858845 }, - { url = "https://files.pythonhosted.org/packages/e9/15/9a22fd26ba5ee8c669d4b8c9c244238e940cd5d818649603ca81d1c69861/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91815221101ad3c6b507804178a7bb5cb7b2ead9ecd600041669c8d805ebd595", size = 1910029 }, - { url = "https://files.pythonhosted.org/packages/d5/33/8cb1a62818974045086f55f604044bf35b9342900318f9a2a029a1bec460/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9fea9c1869bb4742d174a57b4700c6dadea951df8b06de40c2fedb4f02931c2e", size = 1997784 }, - { url = "https://files.pythonhosted.org/packages/c0/ca/49958e4df7715c71773e1ea5be1c74544923d10319173264e6db122543f9/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d20eb4861329bb2484c021b9d9a977566ab16d84000a57e28061151c62b349a", size = 2141075 }, - { url = "https://files.pythonhosted.org/packages/7b/a6/0b3a167a9773c79ba834b959b4e18c3ae9216b8319bd8422792abc8a41b1/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb935c5591573ae3201640579f30128ccc10739b45663f93c06796854405505", size = 2745849 }, - { url = "https://files.pythonhosted.org/packages/0b/60/516484135173aa9e5861d7a0663dce82e4746d2e7f803627d8c25dfa5578/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c964fd24e6166420d18fb53996d8c9fd6eac9bf5ae3ec3d03015be4414ce497f", size = 2005794 }, - { url = "https://files.pythonhosted.org/packages/86/70/05b1eb77459ad47de00cf78ee003016da0cedf8b9170260488d7c21e9181/pydantic_core-2.33.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:681d65e9011f7392db5aa002b7423cc442d6a673c635668c227c6c8d0e5a4f77", size = 2123237 }, - { url = "https://files.pythonhosted.org/packages/c7/57/12667a1409c04ae7dc95d3b43158948eb0368e9c790be8b095cb60611459/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e100c52f7355a48413e2999bfb4e139d2977a904495441b374f3d4fb4a170961", size = 2086351 }, - { url = "https://files.pythonhosted.org/packages/57/61/cc6d1d1c1664b58fdd6ecc64c84366c34ec9b606aeb66cafab6f4088974c/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:048831bd363490be79acdd3232f74a0e9951b11b2b4cc058aeb72b22fdc3abe1", size = 2258914 }, - { url = "https://files.pythonhosted.org/packages/d1/0a/edb137176a1f5419b2ddee8bde6a0a548cfa3c74f657f63e56232df8de88/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bdc84017d28459c00db6f918a7272a5190bec3090058334e43a76afb279eac7c", size = 2257385 }, - { url = "https://files.pythonhosted.org/packages/26/3c/48ca982d50e4b0e1d9954919c887bdc1c2b462801bf408613ccc641b3daa/pydantic_core-2.33.1-cp311-cp311-win32.whl", hash = "sha256:32cd11c5914d1179df70406427097c7dcde19fddf1418c787540f4b730289896", size = 1923765 }, - { url = "https://files.pythonhosted.org/packages/33/cd/7ab70b99e5e21559f5de38a0928ea84e6f23fdef2b0d16a6feaf942b003c/pydantic_core-2.33.1-cp311-cp311-win_amd64.whl", hash = "sha256:2ea62419ba8c397e7da28a9170a16219d310d2cf4970dbc65c32faf20d828c83", size = 1950688 }, - { url = "https://files.pythonhosted.org/packages/4b/ae/db1fc237b82e2cacd379f63e3335748ab88b5adde98bf7544a1b1bd10a84/pydantic_core-2.33.1-cp311-cp311-win_arm64.whl", hash = "sha256:fc903512177361e868bc1f5b80ac8c8a6e05fcdd574a5fb5ffeac5a9982b9e89", size = 1908185 }, - { url = "https://files.pythonhosted.org/packages/c8/ce/3cb22b07c29938f97ff5f5bb27521f95e2ebec399b882392deb68d6c440e/pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8", size = 2026640 }, - { url = "https://files.pythonhosted.org/packages/19/78/f381d643b12378fee782a72126ec5d793081ef03791c28a0fd542a5bee64/pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498", size = 1852649 }, - { url = "https://files.pythonhosted.org/packages/9d/2b/98a37b80b15aac9eb2c6cfc6dbd35e5058a352891c5cce3a8472d77665a6/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939", size = 1892472 }, - { url = "https://files.pythonhosted.org/packages/4e/d4/3c59514e0f55a161004792b9ff3039da52448f43f5834f905abef9db6e4a/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d", size = 1977509 }, - { url = "https://files.pythonhosted.org/packages/a9/b6/c2c7946ef70576f79a25db59a576bce088bdc5952d1b93c9789b091df716/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e", size = 2128702 }, - { url = "https://files.pythonhosted.org/packages/88/fe/65a880f81e3f2a974312b61f82a03d85528f89a010ce21ad92f109d94deb/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3", size = 2679428 }, - { url = "https://files.pythonhosted.org/packages/6f/ff/4459e4146afd0462fb483bb98aa2436d69c484737feaceba1341615fb0ac/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d", size = 2008753 }, - { url = "https://files.pythonhosted.org/packages/7c/76/1c42e384e8d78452ededac8b583fe2550c84abfef83a0552e0e7478ccbc3/pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b", size = 2114849 }, - { url = "https://files.pythonhosted.org/packages/00/72/7d0cf05095c15f7ffe0eb78914b166d591c0eed72f294da68378da205101/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39", size = 2069541 }, - { url = "https://files.pythonhosted.org/packages/b3/69/94a514066bb7d8be499aa764926937409d2389c09be0b5107a970286ef81/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a", size = 2239225 }, - { url = "https://files.pythonhosted.org/packages/84/b0/e390071eadb44b41f4f54c3cef64d8bf5f9612c92686c9299eaa09e267e2/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db", size = 2248373 }, - { url = "https://files.pythonhosted.org/packages/d6/b2/288b3579ffc07e92af66e2f1a11be3b056fe1214aab314748461f21a31c3/pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda", size = 1907034 }, - { url = "https://files.pythonhosted.org/packages/02/28/58442ad1c22b5b6742b992ba9518420235adced665513868f99a1c2638a5/pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4", size = 1956848 }, - { url = "https://files.pythonhosted.org/packages/a1/eb/f54809b51c7e2a1d9f439f158b8dd94359321abcc98767e16fc48ae5a77e/pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea", size = 1903986 }, - { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 }, - { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 }, - { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 }, - { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 }, - { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 }, - { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 }, - { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 }, - { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 }, - { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 }, - { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 }, - { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 }, - { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 }, - { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 }, - { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 }, - { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 }, - { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 }, - { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 }, - { url = "https://files.pythonhosted.org/packages/9c/c7/8b311d5adb0fe00a93ee9b4e92a02b0ec08510e9838885ef781ccbb20604/pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c834f54f8f4640fd7e4b193f80eb25a0602bba9e19b3cd2fc7ffe8199f5ae02", size = 2041659 }, - { url = "https://files.pythonhosted.org/packages/8a/d6/4f58d32066a9e26530daaf9adc6664b01875ae0691570094968aaa7b8fcc/pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:049e0de24cf23766f12cc5cc71d8abc07d4a9deb9061b334b62093dedc7cb068", size = 1873294 }, - { url = "https://files.pythonhosted.org/packages/f7/3f/53cc9c45d9229da427909c751f8ed2bf422414f7664ea4dde2d004f596ba/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a28239037b3d6f16916a4c831a5a0eadf856bdd6d2e92c10a0da3a59eadcf3e", size = 1903771 }, - { url = "https://files.pythonhosted.org/packages/f0/49/bf0783279ce674eb9903fb9ae43f6c614cb2f1c4951370258823f795368b/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d3da303ab5f378a268fa7d45f37d7d85c3ec19769f28d2cc0c61826a8de21fe", size = 2083558 }, - { url = "https://files.pythonhosted.org/packages/9c/5b/0d998367687f986c7d8484a2c476d30f07bf5b8b1477649a6092bd4c540e/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25626fb37b3c543818c14821afe0fd3830bc327a43953bc88db924b68c5723f1", size = 2118038 }, - { url = "https://files.pythonhosted.org/packages/b3/33/039287d410230ee125daee57373ac01940d3030d18dba1c29cd3089dc3ca/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3ab2d36e20fbfcce8f02d73c33a8a7362980cff717926bbae030b93ae46b56c7", size = 2079315 }, - { url = "https://files.pythonhosted.org/packages/1f/85/6d8b2646d99c062d7da2d0ab2faeb0d6ca9cca4c02da6076376042a20da3/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2f9284e11c751b003fd4215ad92d325d92c9cb19ee6729ebd87e3250072cdcde", size = 2249063 }, - { url = "https://files.pythonhosted.org/packages/17/d7/c37d208d5738f7b9ad8f22ae8a727d88ebf9c16c04ed2475122cc3f7224a/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:048c01eee07d37cbd066fc512b9d8b5ea88ceeb4e629ab94b3e56965ad655add", size = 2254631 }, - { url = "https://files.pythonhosted.org/packages/13/e0/bafa46476d328e4553b85ab9b2f7409e7aaef0ce4c937c894821c542d347/pydantic_core-2.33.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5ccd429694cf26af7997595d627dd2637e7932214486f55b8a357edaac9dae8c", size = 2080877 }, - { url = "https://files.pythonhosted.org/packages/0b/76/1794e440c1801ed35415238d2c728f26cd12695df9057154ad768b7b991c/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a371dc00282c4b84246509a5ddc808e61b9864aa1eae9ecc92bb1268b82db4a", size = 2042858 }, - { url = "https://files.pythonhosted.org/packages/73/b4/9cd7b081fb0b1b4f8150507cd59d27b275c3e22ad60b35cb19ea0977d9b9/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f59295ecc75a1788af8ba92f2e8c6eeaa5a94c22fc4d151e8d9638814f85c8fc", size = 1873745 }, - { url = "https://files.pythonhosted.org/packages/e1/d7/9ddb7575d4321e40d0363903c2576c8c0c3280ebea137777e5ab58d723e3/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08530b8ac922003033f399128505f513e30ca770527cc8bbacf75a84fcc2c74b", size = 1904188 }, - { url = "https://files.pythonhosted.org/packages/d1/a8/3194ccfe461bb08da19377ebec8cb4f13c9bd82e13baebc53c5c7c39a029/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae370459da6a5466978c0eacf90690cb57ec9d533f8e63e564ef3822bfa04fe", size = 2083479 }, - { url = "https://files.pythonhosted.org/packages/42/c7/84cb569555d7179ca0b3f838cef08f66f7089b54432f5b8599aac6e9533e/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3de2777e3b9f4d603112f78006f4ae0acb936e95f06da6cb1a45fbad6bdb4b5", size = 2118415 }, - { url = "https://files.pythonhosted.org/packages/3b/67/72abb8c73e0837716afbb58a59cc9e3ae43d1aa8677f3b4bc72c16142716/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a64e81e8cba118e108d7126362ea30e021291b7805d47e4896e52c791be2761", size = 2079623 }, - { url = "https://files.pythonhosted.org/packages/0b/cd/c59707e35a47ba4cbbf153c3f7c56420c58653b5801b055dc52cccc8e2dc/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:52928d8c1b6bda03cc6d811e8923dffc87a2d3c8b3bfd2ce16471c7147a24850", size = 2250175 }, - { url = "https://files.pythonhosted.org/packages/84/32/e4325a6676b0bed32d5b084566ec86ed7fd1e9bcbfc49c578b1755bde920/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1b30d92c9412beb5ac6b10a3eb7ef92ccb14e3f2a8d7732e2d739f58b3aa7544", size = 2254674 }, - { url = "https://files.pythonhosted.org/packages/12/6f/5596dc418f2e292ffc661d21931ab34591952e2843e7168ea5a52591f6ff/pydantic_core-2.33.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f995719707e0e29f0f41a8aa3bcea6e761a36c9136104d3189eafb83f5cec5e5", size = 2080951 }, +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817 }, + { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357 }, + { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011 }, + { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730 }, + { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178 }, + { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462 }, + { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652 }, + { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306 }, + { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720 }, + { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915 }, + { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884 }, + { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496 }, + { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019 }, + { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584 }, + { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071 }, + { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823 }, + { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792 }, + { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338 }, + { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998 }, + { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200 }, + { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890 }, + { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359 }, + { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883 }, + { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074 }, + { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538 }, + { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909 }, + { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786 }, + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000 }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996 }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957 }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199 }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296 }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109 }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028 }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044 }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881 }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034 }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187 }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628 }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866 }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894 }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688 }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808 }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580 }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859 }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810 }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498 }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611 }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924 }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196 }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389 }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223 }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162 }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560 }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 }, + { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982 }, + { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412 }, + { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749 }, + { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527 }, + { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225 }, + { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490 }, + { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525 }, + { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446 }, + { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678 }, + { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200 }, + { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123 }, + { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852 }, + { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484 }, + { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896 }, + { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475 }, + { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013 }, + { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715 }, + { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757 }, ] [[package]] @@ -1476,7 +1485,7 @@ wheels = [ [[package]] name = "pyinstaller" -version = "6.13.0" +version = "6.14.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "altgraph" }, @@ -1487,32 +1496,32 @@ dependencies = [ { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/b1/2949fe6d3874e961898ca5cfc1bf2cf13bdeea488b302e74a745bc28c8ba/pyinstaller-6.13.0.tar.gz", hash = "sha256:38911feec2c5e215e5159a7e66fdb12400168bd116143b54a8a7a37f08733456", size = 4276427 } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/d66d3a9c34349d73eb099401060e2591da8ccc5ed427e54fff3961302513/pyinstaller-6.14.1.tar.gz", hash = "sha256:35d5c06a668e21f0122178dbf20e40fd21012dc8f6170042af6050c4e7b3edca", size = 4284317 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/02/d1a347d35b1b627da1e148159e617576555619ac3bb8bbd5fed661fc7bb5/pyinstaller-6.13.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:aa404f0b02cd57948098055e76ee190b8e65ccf7a2a3f048e5000f668317069f", size = 1001923 }, - { url = "https://files.pythonhosted.org/packages/6b/80/6da39f7aeac65c9ca5afad0fac37887d75fdfd480178a7077c9d30b0704c/pyinstaller-6.13.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:92efcf2f09e78f07b568c5cb7ed48c9940f5dad627af4b49bede6320fab2a06e", size = 718135 }, - { url = "https://files.pythonhosted.org/packages/05/2c/d21d31f780a489609e7bf6385c0f7635238dc98b37cba8645b53322b7450/pyinstaller-6.13.0-py3-none-manylinux2014_i686.whl", hash = "sha256:9f82f113c463f012faa0e323d952ca30a6f922685d9636e754bd3a256c7ed200", size = 728543 }, - { url = "https://files.pythonhosted.org/packages/e1/20/e6ca87bbed6c0163533195707f820f05e10b8da1223fc6972cfe3c3c50c7/pyinstaller-6.13.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:db0e7945ebe276f604eb7c36e536479556ab32853412095e19172a5ec8fca1c5", size = 726868 }, - { url = "https://files.pythonhosted.org/packages/20/d5/53b19285f8817ab6c4b07c570208d62606bab0e5a049d50c93710a1d9dc6/pyinstaller-6.13.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:92fe7337c5aa08d42b38d7a79614492cb571489f2cb0a8f91dc9ef9ccbe01ed3", size = 725037 }, - { url = "https://files.pythonhosted.org/packages/84/5b/08e0b305ba71e6d7cb247e27d714da7536895b0283132d74d249bf662366/pyinstaller-6.13.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:bc09795f5954135dd4486c1535650958c8218acb954f43860e4b05fb515a21c0", size = 721027 }, - { url = "https://files.pythonhosted.org/packages/1f/9c/d8d0a7120103471be8dbe1c5419542aa794b9b9ec2ef628b542f9e6f9ef0/pyinstaller-6.13.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:589937548d34978c568cfdc39f31cf386f45202bc27fdb8facb989c79dfb4c02", size = 723443 }, - { url = "https://files.pythonhosted.org/packages/52/c7/8a9d81569dda2352068ecc6ee779d5feff6729569dd1b4ffd1236ecd38fe/pyinstaller-6.13.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:b7260832f7501ba1d2ce1834d4cddc0f2b94315282bc89c59333433715015447", size = 719915 }, - { url = "https://files.pythonhosted.org/packages/d5/e6/cccadb02b90198c7ed4ffb8bc34d420efb72b996f47cbd4738067a602d65/pyinstaller-6.13.0-py3-none-win32.whl", hash = "sha256:80c568848529635aa7ca46d8d525f68486d53e03f68b7bb5eba2c88d742e302c", size = 1294997 }, - { url = "https://files.pythonhosted.org/packages/1a/06/15cbe0e25d1e73d5b981fa41ff0bb02b15e924e30b8c61256f4a28c4c837/pyinstaller-6.13.0-py3-none-win_amd64.whl", hash = "sha256:8d4296236b85aae570379488c2da833b28828b17c57c2cc21fccd7e3811fe372", size = 1352714 }, - { url = "https://files.pythonhosted.org/packages/83/ef/74379298d46e7caa6aa7ceccc865106d3d4b15ac487ffdda2a35bfb6fe79/pyinstaller-6.13.0-py3-none-win_arm64.whl", hash = "sha256:d9f21d56ca2443aa6a1e255e7ad285c76453893a454105abe1b4d45e92bb9a20", size = 1293589 }, + { url = "https://files.pythonhosted.org/packages/43/f6/fa56e547fe849db4b8da0acaad6101a6382c18370c7e0f378a1cf0ea89f0/pyinstaller-6.14.1-py3-none-macosx_10_13_universal2.whl", hash = "sha256:da559cfe4f7a20a7ebdafdf12ea2a03ea94d3caa49736ef53ee2c155d78422c9", size = 999937 }, + { url = "https://files.pythonhosted.org/packages/af/a6/a2814978f47ae038b1ce112717adbdcfd8dfb9504e5c52437902331cde1a/pyinstaller-6.14.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:f040d1e3d42af3730104078d10d4a8ca3350bd1c78de48f12e1b26f761e0cbc3", size = 719569 }, + { url = "https://files.pythonhosted.org/packages/35/f0/86391a4c0f558aef43a7dac8f678d46f4e5b84bd133308e3ea81f7384ab9/pyinstaller-6.14.1-py3-none-manylinux2014_i686.whl", hash = "sha256:7b8813fb2d5a82ef4ceffc342ed9a11a6fc1ef21e68e833dbd8fedb8a188d3f5", size = 729824 }, + { url = "https://files.pythonhosted.org/packages/e5/88/446814e335d937406e6e1ae4a77ed922b8eea8b90f3aaf69427a16b58ed2/pyinstaller-6.14.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e2cfdbc6dd41d19872054fc233da18856ec422a7fdea899b6985ae04f980376a", size = 727937 }, + { url = "https://files.pythonhosted.org/packages/c6/0f/5aa891c61d303ad4a794b7e2f864aacf64fe0f6f5559e2aec0f742595fad/pyinstaller-6.14.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:a4d53b3ecb5786b097b79bda88c4089186fc1498ef7eaa6cee57599ae459241e", size = 724762 }, + { url = "https://files.pythonhosted.org/packages/c5/92/e32ec0a1754852a8ed5a60f6746c6483e3da68aee97d314f3a3a99e0ed9e/pyinstaller-6.14.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c48dd257f77f61ebea2d1fdbaf11243730f2271873c88d3b5ecb7869525d3bcb", size = 724957 }, + { url = "https://files.pythonhosted.org/packages/c3/66/1260f384e47bf939f6238f791d4cda7edb94771d2fa0a451e0edb21ac9c7/pyinstaller-6.14.1-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:5b05cbb2ffc033b4681268159b82bac94b875475c339603c7e605f00a73c8746", size = 724132 }, + { url = "https://files.pythonhosted.org/packages/d2/8b/8570ab94ec07e0b2b1203f45840353ee76aa067a2540c97da43d43477b26/pyinstaller-6.14.1-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d5fd73757c8ea9adb2f9c1f81656335ba9890029ede3031835d768fde36e89f0", size = 723847 }, + { url = "https://files.pythonhosted.org/packages/d5/43/6c68dc9e53b09ff948d6e46477932b387832bbb920c48061d734ef089368/pyinstaller-6.14.1-py3-none-win32.whl", hash = "sha256:547f7a93592e408cbfd093ce9fd9631215387dab0dbf3130351d3b0b1186a534", size = 1299744 }, + { url = "https://files.pythonhosted.org/packages/7c/dd/bb8d5bcb0592f7f5d454ad308051d00ed34f8b08d5003400b825cfe35513/pyinstaller-6.14.1-py3-none-win_amd64.whl", hash = "sha256:0794290b4b56ef9d35858334deb29f36ec1e1f193b0f825212a0aa5a1bec5a2f", size = 1357625 }, + { url = "https://files.pythonhosted.org/packages/89/57/8a8979737980e50aa5031b77318ce783759bf25be2956317f2e1d7a65a09/pyinstaller-6.14.1-py3-none-win_arm64.whl", hash = "sha256:d9d99695827f892cb19644106da30681363e8ff27b8326ac8416d62890ab9c74", size = 1298607 }, ] [[package]] name = "pyinstaller-hooks-contrib" -version = "2025.3" +version = "2025.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/18/46/195324574e44e52c1ba7f7b0607bc9d488b057d93e253918f1a2759d6a98/pyinstaller_hooks_contrib-2025.3.tar.gz", hash = "sha256:af129da5cd6219669fbda360e295cc822abac55b7647d03fec63a8fcf0a608cf", size = 162501 } +sdist = { url = "https://files.pythonhosted.org/packages/5f/ff/e3376595935d5f8135964d2177cd3e3e0c1b5a6237497d9775237c247a5d/pyinstaller_hooks_contrib-2025.5.tar.gz", hash = "sha256:707386770b8fe066c04aad18a71bc483c7b25e18b4750a756999f7da2ab31982", size = 163124 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/98/0273ffc4f85a4038c8d316a75ef5ac1f10f1bbe5ba50c27871b73da2e3d2/pyinstaller_hooks_contrib-2025.3-py3-none-any.whl", hash = "sha256:70cba46b1a6b82ae9104f074c25926e31f3dde50ff217434d1d660355b949683", size = 434307 }, + { url = "https://files.pythonhosted.org/packages/0c/2c/b4d317534e17dd1df95c394d4b37febb15ead006a1c07c2bb006481fb5e7/pyinstaller_hooks_contrib-2025.5-py3-none-any.whl", hash = "sha256:ebfae1ba341cb0002fb2770fad0edf2b3e913c2728d92df7ad562260988ca373", size = 437246 }, ] [[package]] @@ -1538,7 +1547,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.3.5" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1546,11 +1555,12 @@ dependencies = [ { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714 } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474 }, ] [[package]] @@ -1650,14 +1660,14 @@ wheels = [ [[package]] name = "qrcode" -version = "8.1" +version = "8.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/d4/d222d00f65c81945b55e8f64011c33cb11a2931957ba3e2845fb0874fffe/qrcode-8.1.tar.gz", hash = "sha256:e8df73caf72c3bace3e93d9fa0af5aa78267d4f3f5bc7ab1b208f271605a5e48", size = 41549 } +sdist = { url = "https://files.pythonhosted.org/packages/8f/b2/7fc2931bfae0af02d5f53b174e9cf701adbb35f39d69c2af63d4a39f81a9/qrcode-8.2.tar.gz", hash = "sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c", size = 43317 } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/e6/273de1f5cda537b00bc2947082be747f1d76358db8b945f3a60837bcd0f6/qrcode-8.1-py3-none-any.whl", hash = "sha256:9beba317d793ab8b3838c52af72e603b8ad2599c4e9bbd5c3da37c7dcc13c5cf", size = 45711 }, + { url = "https://files.pythonhosted.org/packages/dd/b8/d2d6d731733f51684bbf76bf34dab3b70a9148e8f2cef2bb544fccec681a/qrcode-8.2-py3-none-any.whl", hash = "sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f", size = 45986 }, ] [[package]] @@ -1674,7 +1684,7 @@ wheels = [ [[package]] name = "requests" -version = "2.32.3" +version = "2.32.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -1682,9 +1692,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847 }, ] [[package]] @@ -1703,36 +1713,36 @@ wheels = [ [[package]] name = "ruff" -version = "0.11.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5b/89/6f9c9674818ac2e9cc2f2b35b704b7768656e6b7c139064fc7ba8fbc99f1/ruff-0.11.7.tar.gz", hash = "sha256:655089ad3224070736dc32844fde783454f8558e71f501cb207485fe4eee23d4", size = 4054861 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/ec/21927cb906c5614b786d1621dba405e3d44f6e473872e6df5d1a6bca0455/ruff-0.11.7-py3-none-linux_armv6l.whl", hash = "sha256:d29e909d9a8d02f928d72ab7837b5cbc450a5bdf578ab9ebee3263d0a525091c", size = 10245403 }, - { url = "https://files.pythonhosted.org/packages/e2/af/fec85b6c2c725bcb062a354dd7cbc1eed53c33ff3aa665165871c9c16ddf/ruff-0.11.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dd1fb86b168ae349fb01dd497d83537b2c5541fe0626e70c786427dd8363aaee", size = 11007166 }, - { url = "https://files.pythonhosted.org/packages/31/9a/2d0d260a58e81f388800343a45898fd8df73c608b8261c370058b675319a/ruff-0.11.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d3d7d2e140a6fbbc09033bce65bd7ea29d6a0adeb90b8430262fbacd58c38ada", size = 10378076 }, - { url = "https://files.pythonhosted.org/packages/c2/c4/9b09b45051404d2e7dd6d9dbcbabaa5ab0093f9febcae664876a77b9ad53/ruff-0.11.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4809df77de390a1c2077d9b7945d82f44b95d19ceccf0c287c56e4dc9b91ca64", size = 10557138 }, - { url = "https://files.pythonhosted.org/packages/5e/5e/f62a1b6669870a591ed7db771c332fabb30f83c967f376b05e7c91bccd14/ruff-0.11.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3a0c2e169e6b545f8e2dba185eabbd9db4f08880032e75aa0e285a6d3f48201", size = 10095726 }, - { url = "https://files.pythonhosted.org/packages/45/59/a7aa8e716f4cbe07c3500a391e58c52caf665bb242bf8be42c62adef649c/ruff-0.11.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49b888200a320dd96a68e86736cf531d6afba03e4f6cf098401406a257fcf3d6", size = 11672265 }, - { url = "https://files.pythonhosted.org/packages/dd/e3/101a8b707481f37aca5f0fcc3e42932fa38b51add87bfbd8e41ab14adb24/ruff-0.11.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2b19cdb9cf7dae00d5ee2e7c013540cdc3b31c4f281f1dacb5a799d610e90db4", size = 12331418 }, - { url = "https://files.pythonhosted.org/packages/dd/71/037f76cbe712f5cbc7b852e4916cd3cf32301a30351818d32ab71580d1c0/ruff-0.11.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64e0ee994c9e326b43539d133a36a455dbaab477bc84fe7bfbd528abe2f05c1e", size = 11794506 }, - { url = "https://files.pythonhosted.org/packages/ca/de/e450b6bab1fc60ef263ef8fcda077fb4977601184877dce1c59109356084/ruff-0.11.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bad82052311479a5865f52c76ecee5d468a58ba44fb23ee15079f17dd4c8fd63", size = 13939084 }, - { url = "https://files.pythonhosted.org/packages/0e/2c/1e364cc92970075d7d04c69c928430b23e43a433f044474f57e425cbed37/ruff-0.11.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7940665e74e7b65d427b82bffc1e46710ec7f30d58b4b2d5016e3f0321436502", size = 11450441 }, - { url = "https://files.pythonhosted.org/packages/9d/7d/1b048eb460517ff9accd78bca0fa6ae61df2b276010538e586f834f5e402/ruff-0.11.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:169027e31c52c0e36c44ae9a9c7db35e505fee0b39f8d9fca7274a6305295a92", size = 10441060 }, - { url = "https://files.pythonhosted.org/packages/3a/57/8dc6ccfd8380e5ca3d13ff7591e8ba46a3b330323515a4996b991b10bd5d/ruff-0.11.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:305b93f9798aee582e91e34437810439acb28b5fc1fee6b8205c78c806845a94", size = 10058689 }, - { url = "https://files.pythonhosted.org/packages/23/bf/20487561ed72654147817885559ba2aa705272d8b5dee7654d3ef2dbf912/ruff-0.11.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a681db041ef55550c371f9cd52a3cf17a0da4c75d6bd691092dfc38170ebc4b6", size = 11073703 }, - { url = "https://files.pythonhosted.org/packages/9d/27/04f2db95f4ef73dccedd0c21daf9991cc3b7f29901a4362057b132075aa4/ruff-0.11.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:07f1496ad00a4a139f4de220b0c97da6d4c85e0e4aa9b2624167b7d4d44fd6b6", size = 11532822 }, - { url = "https://files.pythonhosted.org/packages/e1/72/43b123e4db52144c8add336581de52185097545981ff6e9e58a21861c250/ruff-0.11.7-py3-none-win32.whl", hash = "sha256:f25dfb853ad217e6e5f1924ae8a5b3f6709051a13e9dad18690de6c8ff299e26", size = 10362436 }, - { url = "https://files.pythonhosted.org/packages/c5/a0/3e58cd76fdee53d5c8ce7a56d84540833f924ccdf2c7d657cb009e604d82/ruff-0.11.7-py3-none-win_amd64.whl", hash = "sha256:0a931d85959ceb77e92aea4bbedfded0a31534ce191252721128f77e5ae1f98a", size = 11566676 }, - { url = "https://files.pythonhosted.org/packages/68/ca/69d7c7752bce162d1516e5592b1cc6b6668e9328c0d270609ddbeeadd7cf/ruff-0.11.7-py3-none-win_arm64.whl", hash = "sha256:778c1e5d6f9e91034142dfd06110534ca13220bfaad5c3735f6cb844654f6177", size = 10677936 }, +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/90/5255432602c0b196a0da6720f6f76b93eb50baef46d3c9b0025e2f9acbf3/ruff-0.12.0.tar.gz", hash = "sha256:4d047db3662418d4a848a3fdbfaf17488b34b62f527ed6f10cb8afd78135bc5c", size = 4376101 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/fd/b46bb20e14b11ff49dbc74c61de352e0dc07fb650189513631f6fb5fc69f/ruff-0.12.0-py3-none-linux_armv6l.whl", hash = "sha256:5652a9ecdb308a1754d96a68827755f28d5dfb416b06f60fd9e13f26191a8848", size = 10311554 }, + { url = "https://files.pythonhosted.org/packages/e7/d3/021dde5a988fa3e25d2468d1dadeea0ae89dc4bc67d0140c6e68818a12a1/ruff-0.12.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:05ed0c914fabc602fc1f3b42c53aa219e5736cb030cdd85640c32dbc73da74a6", size = 11118435 }, + { url = "https://files.pythonhosted.org/packages/07/a2/01a5acf495265c667686ec418f19fd5c32bcc326d4c79ac28824aecd6a32/ruff-0.12.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:07a7aa9b69ac3fcfda3c507916d5d1bca10821fe3797d46bad10f2c6de1edda0", size = 10466010 }, + { url = "https://files.pythonhosted.org/packages/4c/57/7caf31dd947d72e7aa06c60ecb19c135cad871a0a8a251723088132ce801/ruff-0.12.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7731c3eec50af71597243bace7ec6104616ca56dda2b99c89935fe926bdcd48", size = 10661366 }, + { url = "https://files.pythonhosted.org/packages/e9/ba/aa393b972a782b4bc9ea121e0e358a18981980856190d7d2b6187f63e03a/ruff-0.12.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:952d0630eae628250ab1c70a7fffb641b03e6b4a2d3f3ec6c1d19b4ab6c6c807", size = 10173492 }, + { url = "https://files.pythonhosted.org/packages/d7/50/9349ee777614bc3062fc6b038503a59b2034d09dd259daf8192f56c06720/ruff-0.12.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c021f04ea06966b02614d442e94071781c424ab8e02ec7af2f037b4c1e01cc82", size = 11761739 }, + { url = "https://files.pythonhosted.org/packages/04/8f/ad459de67c70ec112e2ba7206841c8f4eb340a03ee6a5cabc159fe558b8e/ruff-0.12.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d235618283718ee2fe14db07f954f9b2423700919dc688eacf3f8797a11315c", size = 12537098 }, + { url = "https://files.pythonhosted.org/packages/ed/50/15ad9c80ebd3c4819f5bd8883e57329f538704ed57bac680d95cb6627527/ruff-0.12.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0758038f81beec8cc52ca22de9685b8ae7f7cc18c013ec2050012862cc9165", size = 12154122 }, + { url = "https://files.pythonhosted.org/packages/76/e6/79b91e41bc8cc3e78ee95c87093c6cacfa275c786e53c9b11b9358026b3d/ruff-0.12.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:139b3d28027987b78fc8d6cfb61165447bdf3740e650b7c480744873688808c2", size = 11363374 }, + { url = "https://files.pythonhosted.org/packages/db/c3/82b292ff8a561850934549aa9dc39e2c4e783ab3c21debe55a495ddf7827/ruff-0.12.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68853e8517b17bba004152aebd9dd77d5213e503a5f2789395b25f26acac0da4", size = 11587647 }, + { url = "https://files.pythonhosted.org/packages/2b/42/d5760d742669f285909de1bbf50289baccb647b53e99b8a3b4f7ce1b2001/ruff-0.12.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3a9512af224b9ac4757f7010843771da6b2b0935a9e5e76bb407caa901a1a514", size = 10527284 }, + { url = "https://files.pythonhosted.org/packages/19/f6/fcee9935f25a8a8bba4adbae62495c39ef281256693962c2159e8b284c5f/ruff-0.12.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b08df3d96db798e5beb488d4df03011874aff919a97dcc2dd8539bb2be5d6a88", size = 10158609 }, + { url = "https://files.pythonhosted.org/packages/37/fb/057febf0eea07b9384787bfe197e8b3384aa05faa0d6bd844b94ceb29945/ruff-0.12.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6a315992297a7435a66259073681bb0d8647a826b7a6de45c6934b2ca3a9ed51", size = 11141462 }, + { url = "https://files.pythonhosted.org/packages/10/7c/1be8571011585914b9d23c95b15d07eec2d2303e94a03df58294bc9274d4/ruff-0.12.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e55e44e770e061f55a7dbc6e9aed47feea07731d809a3710feda2262d2d4d8a", size = 11641616 }, + { url = "https://files.pythonhosted.org/packages/6a/ef/b960ab4818f90ff59e571d03c3f992828d4683561095e80f9ef31f3d58b7/ruff-0.12.0-py3-none-win32.whl", hash = "sha256:7162a4c816f8d1555eb195c46ae0bd819834d2a3f18f98cc63819a7b46f474fb", size = 10525289 }, + { url = "https://files.pythonhosted.org/packages/34/93/8b16034d493ef958a500f17cda3496c63a537ce9d5a6479feec9558f1695/ruff-0.12.0-py3-none-win_amd64.whl", hash = "sha256:d00b7a157b8fb6d3827b49d3324da34a1e3f93492c1f97b08e222ad7e9b291e0", size = 11598311 }, + { url = "https://files.pythonhosted.org/packages/d0/33/4d3e79e4a84533d6cd526bfb42c020a23256ae5e4265d858bd1287831f7d/ruff-0.12.0-py3-none-win_arm64.whl", hash = "sha256:8cd24580405ad8c1cc64d61725bca091d6b6da7eb3d36f72cc605467069d7e8b", size = 10724946 }, ] [[package]] name = "setuptools" -version = "79.0.1" +version = "80.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/71/b6365e6325b3290e14957b2c3a804a529968c77a049b2ed40c095f749707/setuptools-79.0.1.tar.gz", hash = "sha256:128ce7b8f33c3079fd1b067ecbb4051a66e8526e7b65f6cec075dfc650ddfa88", size = 1367909 } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/6d/b4752b044bf94cb802d88a888dc7d288baaf77d7910b7dedda74b5ceea0c/setuptools-79.0.1-py3-none-any.whl", hash = "sha256:e147c0549f27767ba362f9da434eab9c5dc0045d5304feb602a0af001089fc51", size = 1256281 }, + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486 }, ] [[package]] @@ -1765,19 +1775,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037 }, ] -[[package]] -name = "taskgroup" -version = "0.2.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f0/8d/e218e0160cc1b692e6e0e5ba34e8865dbb171efeb5fc9a704544b3020605/taskgroup-0.2.2.tar.gz", hash = "sha256:078483ac3e78f2e3f973e2edbf6941374fbea81b9c5d0a96f51d297717f4752d", size = 11504 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/b1/74babcc824a57904e919f3af16d86c08b524c0691504baf038ef2d7f655c/taskgroup-0.2.2-py2.py3-none-any.whl", hash = "sha256:e2c53121609f4ae97303e9ea1524304b4de6faf9eb2c9280c7f87976479a52fb", size = 14237 }, -] - [[package]] name = "text-unidecode" version = "1.3" @@ -1837,41 +1834,41 @@ wheels = [ [[package]] name = "tomlkit" -version = "0.13.2" +version = "0.13.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885 } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901 }, ] [[package]] name = "types-python-dateutil" -version = "2.9.0.20241206" +version = "2.9.0.20250516" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/60/47d92293d9bc521cd2301e423a358abfac0ad409b3a1606d8fbae1321961/types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb", size = 13802 } +sdist = { url = "https://files.pythonhosted.org/packages/ef/88/d65ed807393285204ab6e2801e5d11fbbea811adcaa979a2ed3b67a5ef41/types_python_dateutil-2.9.0.20250516.tar.gz", hash = "sha256:13e80d6c9c47df23ad773d54b2826bd52dbbb41be87c3f339381c1700ad21ee5", size = 13943 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/b3/ca41df24db5eb99b00d97f89d7674a90cb6b3134c52fb8121b6d8d30f15c/types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53", size = 14384 }, + { url = "https://files.pythonhosted.org/packages/c5/3f/b0e8db149896005adc938a1e7f371d6d7e9eca4053a29b108978ed15e0c2/types_python_dateutil-2.9.0.20250516-py3-none-any.whl", hash = "sha256:2b2b3f57f9c6a61fba26a9c0ffb9ea5681c9b83e69cd897c6b5f668d9c0cab93", size = 14356 }, ] [[package]] name = "typing-extensions" -version = "4.13.2" +version = "4.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } +sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, + { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839 }, ] [[package]] name = "typing-inspection" -version = "0.4.0" +version = "0.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 }, ] [[package]] @@ -1885,25 +1882,25 @@ wheels = [ [[package]] name = "urllib3" -version = "2.4.0" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795 }, ] [[package]] name = "uvicorn" -version = "0.34.2" +version = "0.34.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815 } +sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483 }, + { url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431 }, ] [package.optional-dependencies] @@ -1951,16 +1948,16 @@ wheels = [ [[package]] name = "virtualenv" -version = "20.30.0" +version = "20.31.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/e0/633e369b91bbc664df47dcb5454b6c7cf441e8f5b9d0c250ce9f0546401e/virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8", size = 4346945 } +sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/ed/3cfeb48175f0671ec430ede81f628f9fb2b1084c9064ca67ebe8c0ed6a05/virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6", size = 4329461 }, + { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982 }, ] [[package]] @@ -1997,67 +1994,82 @@ wheels = [ [[package]] name = "watchfiles" -version = "1.0.5" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/03/e2/8ed598c42057de7aa5d97c472254af4906ff0a59a66699d426fc9ef795d7/watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9", size = 94537 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/4d/d02e6ea147bb7fff5fd109c694a95109612f419abed46548a930e7f7afa3/watchfiles-1.0.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5c40fe7dd9e5f81e0847b1ea64e1f5dd79dd61afbedb57759df06767ac719b40", size = 405632 }, - { url = "https://files.pythonhosted.org/packages/60/31/9ee50e29129d53a9a92ccf1d3992751dc56fc3c8f6ee721be1c7b9c81763/watchfiles-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c0db396e6003d99bb2d7232c957b5f0b5634bbd1b24e381a5afcc880f7373fb", size = 395734 }, - { url = "https://files.pythonhosted.org/packages/ad/8c/759176c97195306f028024f878e7f1c776bda66ccc5c68fa51e699cf8f1d/watchfiles-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b551d4fb482fc57d852b4541f911ba28957d051c8776e79c3b4a51eb5e2a1b11", size = 455008 }, - { url = "https://files.pythonhosted.org/packages/55/1a/5e977250c795ee79a0229e3b7f5e3a1b664e4e450756a22da84d2f4979fe/watchfiles-1.0.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:830aa432ba5c491d52a15b51526c29e4a4b92bf4f92253787f9726fe01519487", size = 459029 }, - { url = "https://files.pythonhosted.org/packages/e6/17/884cf039333605c1d6e296cf5be35fad0836953c3dfd2adb71b72f9dbcd0/watchfiles-1.0.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a16512051a822a416b0d477d5f8c0e67b67c1a20d9acecb0aafa3aa4d6e7d256", size = 488916 }, - { url = "https://files.pythonhosted.org/packages/ef/e0/bcb6e64b45837056c0a40f3a2db3ef51c2ced19fda38484fa7508e00632c/watchfiles-1.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe0cbc787770e52a96c6fda6726ace75be7f840cb327e1b08d7d54eadc3bc85", size = 523763 }, - { url = "https://files.pythonhosted.org/packages/24/e9/f67e9199f3bb35c1837447ecf07e9830ec00ff5d35a61e08c2cd67217949/watchfiles-1.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d363152c5e16b29d66cbde8fa614f9e313e6f94a8204eaab268db52231fe5358", size = 502891 }, - { url = "https://files.pythonhosted.org/packages/23/ed/a6cf815f215632f5c8065e9c41fe872025ffea35aa1f80499f86eae922db/watchfiles-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee32c9a9bee4d0b7bd7cbeb53cb185cf0b622ac761efaa2eba84006c3b3a614", size = 454921 }, - { url = "https://files.pythonhosted.org/packages/92/4c/e14978599b80cde8486ab5a77a821e8a982ae8e2fcb22af7b0886a033ec8/watchfiles-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29c7fd632ccaf5517c16a5188e36f6612d6472ccf55382db6c7fe3fcccb7f59f", size = 631422 }, - { url = "https://files.pythonhosted.org/packages/b2/1a/9263e34c3458f7614b657f974f4ee61fd72f58adce8b436e16450e054efd/watchfiles-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e637810586e6fe380c8bc1b3910accd7f1d3a9a7262c8a78d4c8fb3ba6a2b3d", size = 625675 }, - { url = "https://files.pythonhosted.org/packages/96/1f/1803a18bd6ab04a0766386a19bcfe64641381a04939efdaa95f0e3b0eb58/watchfiles-1.0.5-cp310-cp310-win32.whl", hash = "sha256:cd47d063fbeabd4c6cae1d4bcaa38f0902f8dc5ed168072874ea11d0c7afc1ff", size = 277921 }, - { url = "https://files.pythonhosted.org/packages/c2/3b/29a89de074a7d6e8b4dc67c26e03d73313e4ecf0d6e97e942a65fa7c195e/watchfiles-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:86c0df05b47a79d80351cd179893f2f9c1b1cae49d96e8b3290c7f4bd0ca0a92", size = 291526 }, - { url = "https://files.pythonhosted.org/packages/39/f4/41b591f59021786ef517e1cdc3b510383551846703e03f204827854a96f8/watchfiles-1.0.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:237f9be419e977a0f8f6b2e7b0475ababe78ff1ab06822df95d914a945eac827", size = 405336 }, - { url = "https://files.pythonhosted.org/packages/ae/06/93789c135be4d6d0e4f63e96eea56dc54050b243eacc28439a26482b5235/watchfiles-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0da39ff917af8b27a4bdc5a97ac577552a38aac0d260a859c1517ea3dc1a7c4", size = 395977 }, - { url = "https://files.pythonhosted.org/packages/d2/db/1cd89bd83728ca37054512d4d35ab69b5f12b8aa2ac9be3b0276b3bf06cc/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cfcb3952350e95603f232a7a15f6c5f86c5375e46f0bd4ae70d43e3e063c13d", size = 455232 }, - { url = "https://files.pythonhosted.org/packages/40/90/d8a4d44ffe960517e487c9c04f77b06b8abf05eb680bed71c82b5f2cad62/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68b2dddba7a4e6151384e252a5632efcaa9bc5d1c4b567f3cb621306b2ca9f63", size = 459151 }, - { url = "https://files.pythonhosted.org/packages/6c/da/267a1546f26465dead1719caaba3ce660657f83c9d9c052ba98fb8856e13/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95cf944fcfc394c5f9de794ce581914900f82ff1f855326f25ebcf24d5397418", size = 489054 }, - { url = "https://files.pythonhosted.org/packages/b1/31/33850dfd5c6efb6f27d2465cc4c6b27c5a6f5ed53c6fa63b7263cf5f60f6/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf6cd9f83d7c023b1aba15d13f705ca7b7d38675c121f3cc4a6e25bd0857ee9", size = 523955 }, - { url = "https://files.pythonhosted.org/packages/09/84/b7d7b67856efb183a421f1416b44ca975cb2ea6c4544827955dfb01f7dc2/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:852de68acd6212cd6d33edf21e6f9e56e5d98c6add46f48244bd479d97c967c6", size = 502234 }, - { url = "https://files.pythonhosted.org/packages/71/87/6dc5ec6882a2254cfdd8b0718b684504e737273903b65d7338efaba08b52/watchfiles-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5730f3aa35e646103b53389d5bc77edfbf578ab6dab2e005142b5b80a35ef25", size = 454750 }, - { url = "https://files.pythonhosted.org/packages/3d/6c/3786c50213451a0ad15170d091570d4a6554976cf0df19878002fc96075a/watchfiles-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:18b3bd29954bc4abeeb4e9d9cf0b30227f0f206c86657674f544cb032296acd5", size = 631591 }, - { url = "https://files.pythonhosted.org/packages/1b/b3/1427425ade4e359a0deacce01a47a26024b2ccdb53098f9d64d497f6684c/watchfiles-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba5552a1b07c8edbf197055bc9d518b8f0d98a1c6a73a293bc0726dce068ed01", size = 625370 }, - { url = "https://files.pythonhosted.org/packages/15/ba/f60e053b0b5b8145d682672024aa91370a29c5c921a88977eb565de34086/watchfiles-1.0.5-cp311-cp311-win32.whl", hash = "sha256:2f1fefb2e90e89959447bc0420fddd1e76f625784340d64a2f7d5983ef9ad246", size = 277791 }, - { url = "https://files.pythonhosted.org/packages/50/ed/7603c4e164225c12c0d4e8700b64bb00e01a6c4eeea372292a3856be33a4/watchfiles-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b6e76ceb1dd18c8e29c73f47d41866972e891fc4cc7ba014f487def72c1cf096", size = 291622 }, - { url = "https://files.pythonhosted.org/packages/a2/c2/99bb7c96b4450e36877fde33690ded286ff555b5a5c1d925855d556968a1/watchfiles-1.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:266710eb6fddc1f5e51843c70e3bebfb0f5e77cf4f27129278c70554104d19ed", size = 283699 }, - { url = "https://files.pythonhosted.org/packages/2a/8c/4f0b9bdb75a1bfbd9c78fad7d8854369283f74fe7cf03eb16be77054536d/watchfiles-1.0.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5eb568c2aa6018e26da9e6c86f3ec3fd958cee7f0311b35c2630fa4217d17f2", size = 401511 }, - { url = "https://files.pythonhosted.org/packages/dc/4e/7e15825def77f8bd359b6d3f379f0c9dac4eb09dd4ddd58fd7d14127179c/watchfiles-1.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a04059f4923ce4e856b4b4e5e783a70f49d9663d22a4c3b3298165996d1377f", size = 392715 }, - { url = "https://files.pythonhosted.org/packages/58/65/b72fb817518728e08de5840d5d38571466c1b4a3f724d190cec909ee6f3f/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e380c89983ce6e6fe2dd1e1921b9952fb4e6da882931abd1824c092ed495dec", size = 454138 }, - { url = "https://files.pythonhosted.org/packages/3e/a4/86833fd2ea2e50ae28989f5950b5c3f91022d67092bfec08f8300d8b347b/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe43139b2c0fdc4a14d4f8d5b5d967f7a2777fd3d38ecf5b1ec669b0d7e43c21", size = 458592 }, - { url = "https://files.pythonhosted.org/packages/38/7e/42cb8df8be9a37e50dd3a818816501cf7a20d635d76d6bd65aae3dbbff68/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee0822ce1b8a14fe5a066f93edd20aada932acfe348bede8aa2149f1a4489512", size = 487532 }, - { url = "https://files.pythonhosted.org/packages/fc/fd/13d26721c85d7f3df6169d8b495fcac8ab0dc8f0945ebea8845de4681dab/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0dbcb1c2d8f2ab6e0a81c6699b236932bd264d4cef1ac475858d16c403de74d", size = 522865 }, - { url = "https://files.pythonhosted.org/packages/a1/0d/7f9ae243c04e96c5455d111e21b09087d0eeaf9a1369e13a01c7d3d82478/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2014a2b18ad3ca53b1f6c23f8cd94a18ce930c1837bd891262c182640eb40a6", size = 499887 }, - { url = "https://files.pythonhosted.org/packages/8e/0f/a257766998e26aca4b3acf2ae97dff04b57071e991a510857d3799247c67/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f6ae86d5cb647bf58f9f655fcf577f713915a5d69057a0371bc257e2553234", size = 454498 }, - { url = "https://files.pythonhosted.org/packages/81/79/8bf142575a03e0af9c3d5f8bcae911ee6683ae93a625d349d4ecf4c8f7df/watchfiles-1.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1a7bac2bde1d661fb31f4d4e8e539e178774b76db3c2c17c4bb3e960a5de07a2", size = 630663 }, - { url = "https://files.pythonhosted.org/packages/f1/80/abe2e79f610e45c63a70d271caea90c49bbf93eb00fa947fa9b803a1d51f/watchfiles-1.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ab626da2fc1ac277bbf752446470b367f84b50295264d2d313e28dc4405d663", size = 625410 }, - { url = "https://files.pythonhosted.org/packages/91/6f/bc7fbecb84a41a9069c2c6eb6319f7f7df113adf113e358c57fc1aff7ff5/watchfiles-1.0.5-cp312-cp312-win32.whl", hash = "sha256:9f4571a783914feda92018ef3901dab8caf5b029325b5fe4558c074582815249", size = 277965 }, - { url = "https://files.pythonhosted.org/packages/99/a5/bf1c297ea6649ec59e935ab311f63d8af5faa8f0b86993e3282b984263e3/watchfiles-1.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:360a398c3a19672cf93527f7e8d8b60d8275119c5d900f2e184d32483117a705", size = 291693 }, - { url = "https://files.pythonhosted.org/packages/7f/7b/fd01087cc21db5c47e5beae507b87965db341cce8a86f9eb12bf5219d4e0/watchfiles-1.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:1a2902ede862969077b97523987c38db28abbe09fb19866e711485d9fbf0d417", size = 283287 }, - { url = "https://files.pythonhosted.org/packages/c7/62/435766874b704f39b2fecd8395a29042db2b5ec4005bd34523415e9bd2e0/watchfiles-1.0.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0b289572c33a0deae62daa57e44a25b99b783e5f7aed81b314232b3d3c81a11d", size = 401531 }, - { url = "https://files.pythonhosted.org/packages/6e/a6/e52a02c05411b9cb02823e6797ef9bbba0bfaf1bb627da1634d44d8af833/watchfiles-1.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a056c2f692d65bf1e99c41045e3bdcaea3cb9e6b5a53dcaf60a5f3bd95fc9763", size = 392417 }, - { url = "https://files.pythonhosted.org/packages/3f/53/c4af6819770455932144e0109d4854437769672d7ad897e76e8e1673435d/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9dca99744991fc9850d18015c4f0438865414e50069670f5f7eee08340d8b40", size = 453423 }, - { url = "https://files.pythonhosted.org/packages/cb/d1/8e88df58bbbf819b8bc5cfbacd3c79e01b40261cad0fc84d1e1ebd778a07/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:894342d61d355446d02cd3988a7326af344143eb33a2fd5d38482a92072d9563", size = 458185 }, - { url = "https://files.pythonhosted.org/packages/ff/70/fffaa11962dd5429e47e478a18736d4e42bec42404f5ee3b92ef1b87ad60/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab44e1580924d1ffd7b3938e02716d5ad190441965138b4aa1d1f31ea0877f04", size = 486696 }, - { url = "https://files.pythonhosted.org/packages/39/db/723c0328e8b3692d53eb273797d9a08be6ffb1d16f1c0ba2bdbdc2a3852c/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6f9367b132078b2ceb8d066ff6c93a970a18c3029cea37bfd7b2d3dd2e5db8f", size = 522327 }, - { url = "https://files.pythonhosted.org/packages/cd/05/9fccc43c50c39a76b68343484b9da7b12d42d0859c37c61aec018c967a32/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2e55a9b162e06e3f862fb61e399fe9f05d908d019d87bf5b496a04ef18a970a", size = 499741 }, - { url = "https://files.pythonhosted.org/packages/23/14/499e90c37fa518976782b10a18b18db9f55ea73ca14641615056f8194bb3/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0125f91f70e0732a9f8ee01e49515c35d38ba48db507a50c5bdcad9503af5827", size = 453995 }, - { url = "https://files.pythonhosted.org/packages/61/d9/f75d6840059320df5adecd2c687fbc18960a7f97b55c300d20f207d48aef/watchfiles-1.0.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:13bb21f8ba3248386337c9fa51c528868e6c34a707f729ab041c846d52a0c69a", size = 629693 }, - { url = "https://files.pythonhosted.org/packages/fc/17/180ca383f5061b61406477218c55d66ec118e6c0c51f02d8142895fcf0a9/watchfiles-1.0.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:839ebd0df4a18c5b3c1b890145b5a3f5f64063c2a0d02b13c76d78fe5de34936", size = 624677 }, - { url = "https://files.pythonhosted.org/packages/bf/15/714d6ef307f803f236d69ee9d421763707899d6298d9f3183e55e366d9af/watchfiles-1.0.5-cp313-cp313-win32.whl", hash = "sha256:4a8ec1e4e16e2d5bafc9ba82f7aaecfeec990ca7cd27e84fb6f191804ed2fcfc", size = 277804 }, - { url = "https://files.pythonhosted.org/packages/a8/b4/c57b99518fadf431f3ef47a610839e46e5f8abf9814f969859d1c65c02c7/watchfiles-1.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:f436601594f15bf406518af922a89dcaab416568edb6f65c4e5bbbad1ea45c11", size = 291087 }, - { url = "https://files.pythonhosted.org/packages/1a/03/81f9fcc3963b3fc415cd4b0b2b39ee8cc136c42fb10a36acf38745e9d283/watchfiles-1.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f59b870db1f1ae5a9ac28245707d955c8721dd6565e7f411024fa374b5362d1d", size = 405947 }, - { url = "https://files.pythonhosted.org/packages/54/97/8c4213a852feb64807ec1d380f42d4fc8bfaef896bdbd94318f8fd7f3e4e/watchfiles-1.0.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9475b0093767e1475095f2aeb1d219fb9664081d403d1dff81342df8cd707034", size = 397276 }, - { url = "https://files.pythonhosted.org/packages/78/12/d4464d19860cb9672efa45eec1b08f8472c478ed67dcd30647c51ada7aef/watchfiles-1.0.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc533aa50664ebd6c628b2f30591956519462f5d27f951ed03d6c82b2dfd9965", size = 455550 }, - { url = "https://files.pythonhosted.org/packages/90/fb/b07bcdf1034d8edeaef4c22f3e9e3157d37c5071b5f9492ffdfa4ad4bed7/watchfiles-1.0.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed1cd825158dcaae36acce7b2db33dcbfd12b30c34317a88b8ed80f0541cc57", size = 455542 }, +sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/dd/579d1dc57f0f895426a1211c4ef3b0cb37eb9e642bb04bdcd962b5df206a/watchfiles-1.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc", size = 405757 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/7a0318cd874393344d48c34d53b3dd419466adf59a29ba5b51c88dd18b86/watchfiles-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df", size = 397511 }, + { url = "https://files.pythonhosted.org/packages/06/be/503514656d0555ec2195f60d810eca29b938772e9bfb112d5cd5ad6f6a9e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8412eacef34cae2836d891836a7fff7b754d6bcac61f6c12ba5ca9bc7e427b68", size = 450739 }, + { url = "https://files.pythonhosted.org/packages/4e/0d/a05dd9e5f136cdc29751816d0890d084ab99f8c17b86f25697288ca09bc7/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df670918eb7dd719642e05979fc84704af913d563fd17ed636f7c4783003fdcc", size = 458106 }, + { url = "https://files.pythonhosted.org/packages/f1/fa/9cd16e4dfdb831072b7ac39e7bea986e52128526251038eb481effe9f48e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7642b9bc4827b5518ebdb3b82698ada8c14c7661ddec5fe719f3e56ccd13c97", size = 484264 }, + { url = "https://files.pythonhosted.org/packages/32/04/1da8a637c7e2b70e750a0308e9c8e662ada0cca46211fa9ef24a23937e0b/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:199207b2d3eeaeb80ef4411875a6243d9ad8bc35b07fc42daa6b801cc39cc41c", size = 597612 }, + { url = "https://files.pythonhosted.org/packages/30/01/109f2762e968d3e58c95731a206e5d7d2a7abaed4299dd8a94597250153c/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a479466da6db5c1e8754caee6c262cd373e6e6c363172d74394f4bff3d84d7b5", size = 477242 }, + { url = "https://files.pythonhosted.org/packages/b5/b8/46f58cf4969d3b7bc3ca35a98e739fa4085b0657a1540ccc29a1a0bc016f/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935f9edd022ec13e447e5723a7d14456c8af254544cefbc533f6dd276c9aa0d9", size = 453148 }, + { url = "https://files.pythonhosted.org/packages/a5/cd/8267594263b1770f1eb76914940d7b2d03ee55eca212302329608208e061/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8076a5769d6bdf5f673a19d51da05fc79e2bbf25e9fe755c47595785c06a8c72", size = 626574 }, + { url = "https://files.pythonhosted.org/packages/a1/2f/7f2722e85899bed337cba715723e19185e288ef361360718973f891805be/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86b1e28d4c37e89220e924305cd9f82866bb0ace666943a6e4196c5df4d58dcc", size = 624378 }, + { url = "https://files.pythonhosted.org/packages/bf/20/64c88ec43d90a568234d021ab4b2a6f42a5230d772b987c3f9c00cc27b8b/watchfiles-1.1.0-cp310-cp310-win32.whl", hash = "sha256:d1caf40c1c657b27858f9774d5c0e232089bca9cb8ee17ce7478c6e9264d2587", size = 279829 }, + { url = "https://files.pythonhosted.org/packages/39/5c/a9c1ed33de7af80935e4eac09570de679c6e21c07070aa99f74b4431f4d6/watchfiles-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a89c75a5b9bc329131115a409d0acc16e8da8dfd5867ba59f1dd66ae7ea8fa82", size = 292192 }, + { url = "https://files.pythonhosted.org/packages/8b/78/7401154b78ab484ccaaeef970dc2af0cb88b5ba8a1b415383da444cdd8d3/watchfiles-1.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2", size = 405751 }, + { url = "https://files.pythonhosted.org/packages/76/63/e6c3dbc1f78d001589b75e56a288c47723de28c580ad715eb116639152b5/watchfiles-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c", size = 397313 }, + { url = "https://files.pythonhosted.org/packages/6c/a2/8afa359ff52e99af1632f90cbf359da46184207e893a5f179301b0c8d6df/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d", size = 450792 }, + { url = "https://files.pythonhosted.org/packages/1d/bf/7446b401667f5c64972a57a0233be1104157fc3abf72c4ef2666c1bd09b2/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7", size = 458196 }, + { url = "https://files.pythonhosted.org/packages/58/2f/501ddbdfa3fa874ea5597c77eeea3d413579c29af26c1091b08d0c792280/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c", size = 484788 }, + { url = "https://files.pythonhosted.org/packages/61/1e/9c18eb2eb5c953c96bc0e5f626f0e53cfef4bd19bd50d71d1a049c63a575/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575", size = 597879 }, + { url = "https://files.pythonhosted.org/packages/8b/6c/1467402e5185d89388b4486745af1e0325007af0017c3384cc786fff0542/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8", size = 477447 }, + { url = "https://files.pythonhosted.org/packages/2b/a1/ec0a606bde4853d6c4a578f9391eeb3684a9aea736a8eb217e3e00aa89a1/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f", size = 453145 }, + { url = "https://files.pythonhosted.org/packages/90/b9/ef6f0c247a6a35d689fc970dc7f6734f9257451aefb30def5d100d6246a5/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4", size = 626539 }, + { url = "https://files.pythonhosted.org/packages/34/44/6ffda5537085106ff5aaa762b0d130ac6c75a08015dd1621376f708c94de/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d", size = 624472 }, + { url = "https://files.pythonhosted.org/packages/c3/e3/71170985c48028fa3f0a50946916a14055e741db11c2e7bc2f3b61f4d0e3/watchfiles-1.1.0-cp311-cp311-win32.whl", hash = "sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2", size = 279348 }, + { url = "https://files.pythonhosted.org/packages/89/1b/3e39c68b68a7a171070f81fc2561d23ce8d6859659406842a0e4bebf3bba/watchfiles-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12", size = 292607 }, + { url = "https://files.pythonhosted.org/packages/61/9f/2973b7539f2bdb6ea86d2c87f70f615a71a1fc2dba2911795cea25968aea/watchfiles-1.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a", size = 285056 }, + { url = "https://files.pythonhosted.org/packages/f6/b8/858957045a38a4079203a33aaa7d23ea9269ca7761c8a074af3524fbb240/watchfiles-1.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179", size = 402339 }, + { url = "https://files.pythonhosted.org/packages/80/28/98b222cca751ba68e88521fabd79a4fab64005fc5976ea49b53fa205d1fa/watchfiles-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5", size = 394409 }, + { url = "https://files.pythonhosted.org/packages/86/50/dee79968566c03190677c26f7f47960aff738d32087087bdf63a5473e7df/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297", size = 450939 }, + { url = "https://files.pythonhosted.org/packages/40/45/a7b56fb129700f3cfe2594a01aa38d033b92a33dddce86c8dfdfc1247b72/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0", size = 457270 }, + { url = "https://files.pythonhosted.org/packages/b5/c8/fa5ef9476b1d02dc6b5e258f515fcaaecf559037edf8b6feffcbc097c4b8/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e", size = 483370 }, + { url = "https://files.pythonhosted.org/packages/98/68/42cfcdd6533ec94f0a7aab83f759ec11280f70b11bfba0b0f885e298f9bd/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee", size = 598654 }, + { url = "https://files.pythonhosted.org/packages/d3/74/b2a1544224118cc28df7e59008a929e711f9c68ce7d554e171b2dc531352/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd", size = 478667 }, + { url = "https://files.pythonhosted.org/packages/8c/77/e3362fe308358dc9f8588102481e599c83e1b91c2ae843780a7ded939a35/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f", size = 452213 }, + { url = "https://files.pythonhosted.org/packages/6e/17/c8f1a36540c9a1558d4faf08e909399e8133599fa359bf52ec8fcee5be6f/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4", size = 626718 }, + { url = "https://files.pythonhosted.org/packages/26/45/fb599be38b4bd38032643783d7496a26a6f9ae05dea1a42e58229a20ac13/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f", size = 623098 }, + { url = "https://files.pythonhosted.org/packages/a1/e7/fdf40e038475498e160cd167333c946e45d8563ae4dd65caf757e9ffe6b4/watchfiles-1.1.0-cp312-cp312-win32.whl", hash = "sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd", size = 279209 }, + { url = "https://files.pythonhosted.org/packages/3f/d3/3ae9d5124ec75143bdf088d436cba39812122edc47709cd2caafeac3266f/watchfiles-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47", size = 292786 }, + { url = "https://files.pythonhosted.org/packages/26/2f/7dd4fc8b5f2b34b545e19629b4a018bfb1de23b3a496766a2c1165ca890d/watchfiles-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6", size = 284343 }, + { url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004 }, + { url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671 }, + { url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772 }, + { url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789 }, + { url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551 }, + { url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420 }, + { url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950 }, + { url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706 }, + { url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814 }, + { url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820 }, + { url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194 }, + { url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349 }, + { url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836 }, + { url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343 }, + { url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916 }, + { url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582 }, + { url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752 }, + { url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436 }, + { url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016 }, + { url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727 }, + { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864 }, + { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626 }, + { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744 }, + { url = "https://files.pythonhosted.org/packages/be/7c/a3d7c55cfa377c2f62c4ae3c6502b997186bc5e38156bafcb9b653de9a6d/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a6fd40bbb50d24976eb275ccb55cd1951dfb63dbc27cae3066a6ca5f4beabd5", size = 406748 }, + { url = "https://files.pythonhosted.org/packages/38/d0/c46f1b2c0ca47f3667b144de6f0515f6d1c670d72f2ca29861cac78abaa1/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f811079d2f9795b5d48b55a37aa7773680a5659afe34b54cc1d86590a51507d", size = 398801 }, + { url = "https://files.pythonhosted.org/packages/70/9c/9a6a42e97f92eeed77c3485a43ea96723900aefa3ac739a8c73f4bff2cd7/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2726d7bfd9f76158c84c10a409b77a320426540df8c35be172444394b17f7ea", size = 451528 }, + { url = "https://files.pythonhosted.org/packages/51/7b/98c7f4f7ce7ff03023cf971cd84a3ee3b790021ae7584ffffa0eb2554b96/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df32d59cb9780f66d165a9a7a26f19df2c7d24e3bd58713108b41d0ff4f929c6", size = 454095 }, + { url = "https://files.pythonhosted.org/packages/8c/6b/686dcf5d3525ad17b384fd94708e95193529b460a1b7bf40851f1328ec6e/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3", size = 406910 }, + { url = "https://files.pythonhosted.org/packages/f3/d3/71c2dcf81dc1edcf8af9f4d8d63b1316fb0a2dd90cbfd427e8d9dd584a90/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c", size = 398816 }, + { url = "https://files.pythonhosted.org/packages/b8/fa/12269467b2fc006f8fce4cd6c3acfa77491dd0777d2a747415f28ccc8c60/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432", size = 451584 }, + { url = "https://files.pythonhosted.org/packages/bd/d3/254cea30f918f489db09d6a8435a7de7047f8cb68584477a515f160541d6/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792", size = 454009 }, ] [[package]] @@ -2118,15 +2130,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884 }, { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, ] - -[[package]] -name = "wsproto" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226 }, -]