diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c5fa2a0e95d..e0758519285 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -712,6 +712,17 @@ updates: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] + - package-ecosystem: "gradle" + directory: "/packages/two_dimensional_scrollables/example/android/app" + commit-message: + prefix: "[2D_scrollables]" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: [ "version-update:semver-minor", "version-update:semver-patch" ] + - package-ecosystem: "gradle" directory: "/packages/url_launcher/url_launcher_android/android" commit-message: diff --git a/CODEOWNERS b/CODEOWNERS index 9a0ade20876..68508835d07 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -37,6 +37,7 @@ packages/quick_actions/** @bparrishMines packages/rfw/** @Hixie packages/shared_preferences/** @tarrinneal packages/standard_message_codec/** @jonahwilliams +packages/two_dimensional_scrollables/** @Piinks packages/url_launcher/** @stuartmorgan packages/video_player/** @tarrinneal packages/web_benchmarks/** @yjbanov diff --git a/packages/two_dimensional_scrollables/.gitignore b/packages/two_dimensional_scrollables/.gitignore new file mode 100644 index 00000000000..96486fd9302 --- /dev/null +++ b/packages/two_dimensional_scrollables/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/packages/two_dimensional_scrollables/.metadata b/packages/two_dimensional_scrollables/.metadata new file mode 100644 index 00000000000..686af8a8c72 --- /dev/null +++ b/packages/two_dimensional_scrollables/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "0ed17dd5a24676c897b5268da976717c22e68780" + channel: "[user-branch]" + +project_type: package diff --git a/packages/two_dimensional_scrollables/AUTHORS b/packages/two_dimensional_scrollables/AUTHORS new file mode 100644 index 00000000000..8c0344813dc --- /dev/null +++ b/packages/two_dimensional_scrollables/AUTHORS @@ -0,0 +1,6 @@ +# Below is a list of people and organizations that have contributed +# to the Flutter project. Names should be added to the list like so: +# +# Name/Organization + +Google Inc. \ No newline at end of file diff --git a/packages/two_dimensional_scrollables/CHANGELOG.md b/packages/two_dimensional_scrollables/CHANGELOG.md new file mode 100644 index 00000000000..278f5a069d1 --- /dev/null +++ b/packages/two_dimensional_scrollables/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* Initial release - TableView diff --git a/packages/two_dimensional_scrollables/LICENSE b/packages/two_dimensional_scrollables/LICENSE new file mode 100644 index 00000000000..c6823b81eb8 --- /dev/null +++ b/packages/two_dimensional_scrollables/LICENSE @@ -0,0 +1,25 @@ +Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +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 OWNER 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/two_dimensional_scrollables/README.md b/packages/two_dimensional_scrollables/README.md new file mode 100644 index 00000000000..1b5a0e4b3fd --- /dev/null +++ b/packages/two_dimensional_scrollables/README.md @@ -0,0 +1,68 @@ +# Two Dimensional Scrollables + +A package that provides widgets that scroll in two dimensions, built on the +two-dimensional foundation of the Flutter framework. + +## Features + +This package provides support for a TableView widget that scrolls in both the +vertical and horizontal axes. + +### TableView + +`TableView` is a subclass of `TwoDimensionalScrollView`, building its provided +children lazily in a `TwoDimensionalViewport`. This widget can + +- Scroll diagonally, or lock axes +- Apply decorations to rows and columns +- Handle gestures & custom pointers for rows and columns +- Pin rows and columns + +## Getting started + +### Depend on it + +Run this command with Flutter: + +```sh +$ flutter pub add two_dimensional_scrollables +``` + +### Import it + +Now in your Dart code, you can use: + +```sh +import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; +``` + +## Usage + +### TableView + +The code in `example/` shows a `TableView` of initially 400 cells, each varying +in sizes with a few `TableSpanDecoration`s like background colors and borders. The +`builder` constructor is called on demand for the cells that are visible in the +TableView. Additional rows can be added on demand while the vertical position +can jump between the first and last row using the buttons at the bottom of the +screen. + +## Changelog + +See the +[Changelog](https://github.com/flutter/packages/blob/main/packages/two_dimensional_scrollables/CHANGELOG.md) +for a list of new features and breaking changes. + +## Roadmap + +See the [GitHub project](https://github.com/orgs/flutter/projects/32/) for a +prioritized list of feature requests and known issues. + +## Additional information + +The package uses the two-dimensional foundation from the Flutter framework, +meaning most of the core functionality of 2D scrolling is not implemented here. +This also means any subclass of the foundation can create different 2D scrolling +widgets and be added to the collection. If you want to contribute to +this package, you can open a pull request in [Flutter Packages](https://github.com/flutter/packages) +and add the tag "p: two_dimensional_scrollables". diff --git a/packages/two_dimensional_scrollables/dart_test.yaml b/packages/two_dimensional_scrollables/dart_test.yaml new file mode 100644 index 00000000000..5ea7fe8ae51 --- /dev/null +++ b/packages/two_dimensional_scrollables/dart_test.yaml @@ -0,0 +1,3 @@ +# TODO(Piinks): Web cannot support temp golden files, re-enable once mock_canvas +# makes it to stable. See +test_on: vm diff --git a/packages/two_dimensional_scrollables/example/.gitignore b/packages/two_dimensional_scrollables/example/.gitignore new file mode 100644 index 00000000000..24476c5d1eb --- /dev/null +++ b/packages/two_dimensional_scrollables/example/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/two_dimensional_scrollables/example/.metadata b/packages/two_dimensional_scrollables/example/.metadata new file mode 100644 index 00000000000..9b1076dd78e --- /dev/null +++ b/packages/two_dimensional_scrollables/example/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "f1fefa8315ccf7081343d50815809dc3c7d5f347" + channel: "[user-branch]" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: f1fefa8315ccf7081343d50815809dc3c7d5f347 + base_revision: f1fefa8315ccf7081343d50815809dc3c7d5f347 + - platform: android + create_revision: f1fefa8315ccf7081343d50815809dc3c7d5f347 + base_revision: f1fefa8315ccf7081343d50815809dc3c7d5f347 + - platform: ios + create_revision: f1fefa8315ccf7081343d50815809dc3c7d5f347 + base_revision: f1fefa8315ccf7081343d50815809dc3c7d5f347 + - platform: linux + create_revision: f1fefa8315ccf7081343d50815809dc3c7d5f347 + base_revision: f1fefa8315ccf7081343d50815809dc3c7d5f347 + - platform: macos + create_revision: f1fefa8315ccf7081343d50815809dc3c7d5f347 + base_revision: f1fefa8315ccf7081343d50815809dc3c7d5f347 + - platform: web + create_revision: f1fefa8315ccf7081343d50815809dc3c7d5f347 + base_revision: f1fefa8315ccf7081343d50815809dc3c7d5f347 + - platform: windows + create_revision: f1fefa8315ccf7081343d50815809dc3c7d5f347 + base_revision: f1fefa8315ccf7081343d50815809dc3c7d5f347 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/two_dimensional_scrollables/example/README.md b/packages/two_dimensional_scrollables/example/README.md new file mode 100644 index 00000000000..0f267cb31f7 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/README.md @@ -0,0 +1,3 @@ +# TableView Example + +A sample application that utilizes the TableView API. diff --git a/packages/two_dimensional_scrollables/example/android/.gitignore b/packages/two_dimensional_scrollables/example/android/.gitignore new file mode 100644 index 00000000000..6f568019d3c --- /dev/null +++ b/packages/two_dimensional_scrollables/example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/packages/two_dimensional_scrollables/example/android/app/build.gradle b/packages/two_dimensional_scrollables/example/android/app/build.gradle new file mode 100644 index 00000000000..2f3d3531e36 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/android/app/build.gradle @@ -0,0 +1,73 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + namespace 'dev.flutter.packages.two_dimensional_scrollables.example' + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } + namespace 'com.example.example' +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/packages/two_dimensional_scrollables/example/android/app/src/debug/AndroidManifest.xml b/packages/two_dimensional_scrollables/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000000..399f6981d5d --- /dev/null +++ b/packages/two_dimensional_scrollables/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/two_dimensional_scrollables/example/android/app/src/main/AndroidManifest.xml b/packages/two_dimensional_scrollables/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..19b862ec8a7 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/packages/two_dimensional_scrollables/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/two_dimensional_scrollables/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 00000000000..56d56eee85f --- /dev/null +++ b/packages/two_dimensional_scrollables/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,10 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/packages/two_dimensional_scrollables/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/two_dimensional_scrollables/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000000..f74085f3f6a --- /dev/null +++ b/packages/two_dimensional_scrollables/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/two_dimensional_scrollables/example/android/app/src/main/res/drawable/launch_background.xml b/packages/two_dimensional_scrollables/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000000..304732f8842 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/two_dimensional_scrollables/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/two_dimensional_scrollables/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000000..db77bb4b7b0 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/two_dimensional_scrollables/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/two_dimensional_scrollables/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000000..17987b79bb8 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/two_dimensional_scrollables/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/two_dimensional_scrollables/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000000..09d4391482b Binary files /dev/null and b/packages/two_dimensional_scrollables/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/two_dimensional_scrollables/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/two_dimensional_scrollables/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000000..d5f1c8d34e7 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/two_dimensional_scrollables/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/two_dimensional_scrollables/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000000..4d6372eebdb Binary files /dev/null and b/packages/two_dimensional_scrollables/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/two_dimensional_scrollables/example/android/app/src/main/res/values-night/styles.xml b/packages/two_dimensional_scrollables/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000000..06952be745f --- /dev/null +++ b/packages/two_dimensional_scrollables/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/two_dimensional_scrollables/example/android/app/src/main/res/values/styles.xml b/packages/two_dimensional_scrollables/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000000..cb1ef88056e --- /dev/null +++ b/packages/two_dimensional_scrollables/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/two_dimensional_scrollables/example/android/app/src/profile/AndroidManifest.xml b/packages/two_dimensional_scrollables/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000000..399f6981d5d --- /dev/null +++ b/packages/two_dimensional_scrollables/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/two_dimensional_scrollables/example/android/build.gradle b/packages/two_dimensional_scrollables/example/android/build.gradle new file mode 100644 index 00000000000..582d60a2faa --- /dev/null +++ b/packages/two_dimensional_scrollables/example/android/build.gradle @@ -0,0 +1,37 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.4.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + // See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. + def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' + if (System.getenv().containsKey(artifactRepoKey)) { + println "Using artifact hub" + maven { url System.getenv(artifactRepoKey) } + } + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/packages/two_dimensional_scrollables/example/android/gradle.properties b/packages/two_dimensional_scrollables/example/android/gradle.properties new file mode 100644 index 00000000000..94adc3a3f97 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/two_dimensional_scrollables/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/two_dimensional_scrollables/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..3c472b99c6f --- /dev/null +++ b/packages/two_dimensional_scrollables/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/packages/two_dimensional_scrollables/example/android/settings.gradle b/packages/two_dimensional_scrollables/example/android/settings.gradle new file mode 100644 index 00000000000..5315a9b79e4 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/android/settings.gradle @@ -0,0 +1,24 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" + +// See https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure#gradle-structure for more info. +buildscript { + repositories { + maven { + url "https://plugins.gradle.org/m2/" + } + } + dependencies { + classpath "gradle.plugin.com.google.cloud.artifactregistry:artifactregistry-gradle-plugin:2.2.1" + } +} +apply plugin: "com.google.cloud.artifactregistry.gradle-plugin" diff --git a/packages/two_dimensional_scrollables/example/ios/.gitignore b/packages/two_dimensional_scrollables/example/ios/.gitignore new file mode 100644 index 00000000000..7a7f9873ad7 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/two_dimensional_scrollables/example/ios/Flutter/AppFrameworkInfo.plist b/packages/two_dimensional_scrollables/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000000..9625e105df3 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/packages/two_dimensional_scrollables/example/ios/Flutter/Debug.xcconfig b/packages/two_dimensional_scrollables/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000000..592ceee85b8 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/two_dimensional_scrollables/example/ios/Flutter/Release.xcconfig b/packages/two_dimensional_scrollables/example/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000000..592ceee85b8 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/two_dimensional_scrollables/example/ios/Runner.xcodeproj/project.pbxproj b/packages/two_dimensional_scrollables/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..75c0e507d0b --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,614 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807E294A63A400263BE5 /* Frameworks */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/two_dimensional_scrollables/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/two_dimensional_scrollables/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..919434a6254 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/two_dimensional_scrollables/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/two_dimensional_scrollables/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/two_dimensional_scrollables/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/two_dimensional_scrollables/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000000..f9b0d7c5ea1 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/two_dimensional_scrollables/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/two_dimensional_scrollables/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000000..87131a09bea --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/two_dimensional_scrollables/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/two_dimensional_scrollables/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..1d526a16ed0 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/two_dimensional_scrollables/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/two_dimensional_scrollables/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/two_dimensional_scrollables/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/two_dimensional_scrollables/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000000..f9b0d7c5ea1 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/AppDelegate.swift b/packages/two_dimensional_scrollables/example/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000000..caf99839333 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..d36b1fab2d9 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000000..dc9ada4725e Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000000..7353c41ecf9 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000000..797d452e458 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000000..6ed2d933e11 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000000..4cd7b0099ca Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000000..fe730945a01 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000000..321773cd857 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000000..797d452e458 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000000..502f463a9bc Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000000..0ec30343922 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000000..0ec30343922 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000000..e9f5fea27c7 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000000..84ac32ae7d9 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000000..8953cba0906 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000000..0467bf12aa4 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000000..0bedcf2fd46 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000000..9da19eacad3 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000000..9da19eacad3 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000000..9da19eacad3 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000000..89c2725b70f --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/two_dimensional_scrollables/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000000..f2e259c7c93 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Base.lproj/Main.storyboard b/packages/two_dimensional_scrollables/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000000..f3c28516fb3 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Info.plist b/packages/two_dimensional_scrollables/example/ios/Runner/Info.plist new file mode 100644 index 00000000000..5458fc4188b --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/packages/two_dimensional_scrollables/example/ios/Runner/Runner-Bridging-Header.h b/packages/two_dimensional_scrollables/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000000..eb7e8ba8052 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1,5 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "GeneratedPluginRegistrant.h" diff --git a/packages/two_dimensional_scrollables/example/ios/RunnerTests/RunnerTests.swift b/packages/two_dimensional_scrollables/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000000..f4dc3f41a54 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,16 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/two_dimensional_scrollables/example/lib/main.dart b/packages/two_dimensional_scrollables/example/lib/main.dart new file mode 100644 index 00000000000..cd23c568ae6 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/lib/main.dart @@ -0,0 +1,188 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; + +// Print statements are only for illustrative purposes, not recommended for +// production applications. +// ignore_for_file: avoid_print + +void main() { + runApp(const TableExampleApp()); +} + +/// A sample application that utilizes the TableView API. +class TableExampleApp extends StatelessWidget { + /// Creates an instance of the TableView example app. + const TableExampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Table Example', + theme: ThemeData( + useMaterial3: true, + ), + home: const TableExample(), + ); + } +} + +/// The class containing the TableView for the sample application. +class TableExample extends StatefulWidget { + /// Creates a screen that demonstrates the TableView widget. + const TableExample({super.key}); + + @override + State createState() => _TableExampleState(); +} + +class _TableExampleState extends State { + late final ScrollController _verticalController = ScrollController(); + int _rowCount = 20; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Table Example'), + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 50), + child: TableView.builder( + verticalDetails: + ScrollableDetails.vertical(controller: _verticalController), + cellBuilder: _buildCell, + columnCount: 20, + columnBuilder: _buildColumnSpan, + rowCount: _rowCount, + rowBuilder: _buildRowSpan, + ), + ), + persistentFooterButtons: [ + TextButton( + onPressed: () { + _verticalController.jumpTo(0); + }, + child: const Text('Jump to Top'), + ), + TextButton( + onPressed: () { + _verticalController + .jumpTo(_verticalController.position.maxScrollExtent); + }, + child: const Text('Jump to Bottom'), + ), + TextButton( + onPressed: () { + setState(() { + _rowCount += 10; + }); + }, + child: const Text('Add 10 Rows'), + ), + ], + ); + } + + Widget _buildCell(BuildContext context, TableVicinity vicinity) { + return Center( + child: Text('Tile c: ${vicinity.column}, r: ${vicinity.row}'), + ); + } + + TableSpan _buildColumnSpan(int index) { + const TableSpanDecoration decoration = TableSpanDecoration( + border: TableSpanBorder( + trailing: BorderSide(), + ), + ); + + switch (index % 5) { + case 0: + return TableSpan( + foregroundDecoration: decoration, + extent: const FixedTableSpanExtent(100), + onEnter: (_) => print('Entered column $index'), + recognizerFactories: { + TapGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + () => TapGestureRecognizer(), + (TapGestureRecognizer t) => + t.onTap = () => print('Tap column $index'), + ), + }, + ); + case 1: + return TableSpan( + foregroundDecoration: decoration, + extent: const FractionalTableSpanExtent(0.5), + onEnter: (_) => print('Entered column $index'), + cursor: SystemMouseCursors.contextMenu, + ); + case 2: + return TableSpan( + foregroundDecoration: decoration, + extent: const FixedTableSpanExtent(120), + onEnter: (_) => print('Entered column $index'), + ); + case 3: + return TableSpan( + foregroundDecoration: decoration, + extent: const FixedTableSpanExtent(145), + onEnter: (_) => print('Entered column $index'), + ); + case 4: + return TableSpan( + foregroundDecoration: decoration, + extent: const FixedTableSpanExtent(200), + onEnter: (_) => print('Entered column $index'), + ); + } + throw AssertionError( + 'This should be unreachable, as every index is accounted for in the switch clauses.'); + } + + TableSpan _buildRowSpan(int index) { + final TableSpanDecoration decoration = TableSpanDecoration( + color: index.isEven ? Colors.purple[100] : null, + border: const TableSpanBorder( + trailing: BorderSide( + width: 3, + ), + ), + ); + + switch (index % 3) { + case 0: + return TableSpan( + backgroundDecoration: decoration, + extent: const FixedTableSpanExtent(50), + recognizerFactories: { + TapGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + () => TapGestureRecognizer(), + (TapGestureRecognizer t) => + t.onTap = () => print('Tap row $index'), + ), + }, + ); + case 1: + return TableSpan( + backgroundDecoration: decoration, + extent: const FixedTableSpanExtent(65), + cursor: SystemMouseCursors.click, + ); + case 2: + return TableSpan( + backgroundDecoration: decoration, + extent: const FractionalTableSpanExtent(0.15), + ); + } + throw AssertionError( + 'This should be unreachable, as every index is accounted for in the switch clauses.'); + } +} diff --git a/packages/two_dimensional_scrollables/example/linux/.gitignore b/packages/two_dimensional_scrollables/example/linux/.gitignore new file mode 100644 index 00000000000..d3896c98444 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/two_dimensional_scrollables/example/linux/CMakeLists.txt b/packages/two_dimensional_scrollables/example/linux/CMakeLists.txt new file mode 100644 index 00000000000..d67bd4e03e2 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/linux/CMakeLists.txt @@ -0,0 +1,139 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/two_dimensional_scrollables/example/linux/flutter/CMakeLists.txt b/packages/two_dimensional_scrollables/example/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000000..d5bd01648a9 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/packages/two_dimensional_scrollables/example/linux/flutter/generated_plugins.cmake b/packages/two_dimensional_scrollables/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000000..2e1de87a7eb --- /dev/null +++ b/packages/two_dimensional_scrollables/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/two_dimensional_scrollables/example/linux/main.cc b/packages/two_dimensional_scrollables/example/linux/main.cc new file mode 100644 index 00000000000..1507d02825e --- /dev/null +++ b/packages/two_dimensional_scrollables/example/linux/main.cc @@ -0,0 +1,10 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/packages/two_dimensional_scrollables/example/linux/my_application.cc b/packages/two_dimensional_scrollables/example/linux/my_application.cc new file mode 100644 index 00000000000..3a67810f561 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/linux/my_application.cc @@ -0,0 +1,111 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/packages/two_dimensional_scrollables/example/linux/my_application.h b/packages/two_dimensional_scrollables/example/linux/my_application.h new file mode 100644 index 00000000000..6e9f0c3ff66 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/linux/my_application.h @@ -0,0 +1,22 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/packages/two_dimensional_scrollables/example/macos/.gitignore b/packages/two_dimensional_scrollables/example/macos/.gitignore new file mode 100644 index 00000000000..746adbb6b9e --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/packages/two_dimensional_scrollables/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/two_dimensional_scrollables/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000000..c2efd0b608b --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/two_dimensional_scrollables/example/macos/Flutter/Flutter-Release.xcconfig b/packages/two_dimensional_scrollables/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000000..c2efd0b608b --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/project.pbxproj b/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..27e0f506b60 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,695 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000000..397f3d339fd --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/two_dimensional_scrollables/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/two_dimensional_scrollables/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..1d526a16ed0 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/two_dimensional_scrollables/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/two_dimensional_scrollables/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/AppDelegate.swift b/packages/two_dimensional_scrollables/example/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000000..5cec4c48f62 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..a2ec33f19f1 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000000..82b6f9d9a33 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000000..13b35eba55c Binary files /dev/null and b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000000..0a3f5fa40fb Binary files /dev/null and b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000000..bdb57226d5f Binary files /dev/null and b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000000..f083318e09c Binary files /dev/null and b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000000..326c0e72c9d Binary files /dev/null and b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000000..2f1632cfddf Binary files /dev/null and b/packages/two_dimensional_scrollables/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/two_dimensional_scrollables/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000000..80e867a4e06 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/two_dimensional_scrollables/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000000..dda192bcdff --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.example + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2023 com.example. All rights reserved. diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/Configs/Debug.xcconfig b/packages/two_dimensional_scrollables/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000000..36b0fd9464f --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/Configs/Release.xcconfig b/packages/two_dimensional_scrollables/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000000..dff4f49561c --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/Configs/Warnings.xcconfig b/packages/two_dimensional_scrollables/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000000..42bcbf4780b --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/DebugProfile.entitlements b/packages/two_dimensional_scrollables/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000000..dddb8a30c85 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/Info.plist b/packages/two_dimensional_scrollables/example/macos/Runner/Info.plist new file mode 100644 index 00000000000..4789daa6a44 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/MainFlutterWindow.swift b/packages/two_dimensional_scrollables/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000000..f21908966e9 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,19 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/two_dimensional_scrollables/example/macos/Runner/Release.entitlements b/packages/two_dimensional_scrollables/example/macos/Runner/Release.entitlements new file mode 100644 index 00000000000..852fa1a4728 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/packages/two_dimensional_scrollables/example/macos/RunnerTests/RunnerTests.swift b/packages/two_dimensional_scrollables/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000000..de34867bac2 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,16 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/two_dimensional_scrollables/example/pubspec.yaml b/packages/two_dimensional_scrollables/example/pubspec.yaml new file mode 100644 index 00000000000..a36e620a556 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/pubspec.yaml @@ -0,0 +1,25 @@ +name: table_view_example +description: 'A sample application that uses TableView' +publish_to: 'none' + +# The following defines the version and build number for your application. +version: 1.0.0+1 + +environment: + sdk: '>=3.1.0 <4.0.0' + flutter: ">=3.13.0" + +dependencies: + flutter: + sdk: flutter + two_dimensional_scrollables: + path: ../ + +dev_dependencies: + flutter_lints: ^2.0.0 + flutter_test: + sdk: flutter + +# The following section is specific to Flutter packages. +flutter: + uses-material-design: true diff --git a/packages/two_dimensional_scrollables/example/test/table_view_example_test.dart b/packages/two_dimensional_scrollables/example/test/table_view_example_test.dart new file mode 100644 index 00000000000..f5cfee64766 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/test/table_view_example_test.dart @@ -0,0 +1,57 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:table_view_example/main.dart'; + +void main() { + testWidgets('Example app builds & scrolls', (WidgetTester tester) async { + await tester.pumpWidget(const TableExampleApp()); + await tester.pump(); + + expect(find.text('Jump to Top'), findsOneWidget); + expect(find.text('Jump to Bottom'), findsOneWidget); + expect(find.text('Add 10 Rows'), findsOneWidget); + + final Finder scrollable = find.byWidgetPredicate((Widget widget) { + if (widget is Scrollable) { + return widget.axisDirection == AxisDirection.down; + } + return false; + }); + final ScrollPosition position = + (tester.state(scrollable) as ScrollableState).position; + expect(position.axis, Axis.vertical); + expect(position.pixels, 0.0); + position.jumpTo(10); + await tester.pump(); + expect(position.pixels, 10.0); + }); + + testWidgets('Example app buttons work', (WidgetTester tester) async { + await tester.pumpWidget(const TableExampleApp()); + await tester.pump(); + + final Finder scrollable = find.byWidgetPredicate((Widget widget) { + if (widget is Scrollable) { + return widget.axisDirection == AxisDirection.down; + } + return false; + }); + final ScrollPosition position = + (tester.state(scrollable) as ScrollableState).position; + + expect(position.maxScrollExtent, greaterThan(750)); + await tester.tap(find.text('Add 10 Rows')); + await tester.pump(); + expect(position.maxScrollExtent, greaterThan(1380)); + await tester.tap(find.text('Jump to Bottom')); + await tester.pump(); + expect(position.pixels, greaterThan(1380)); + await tester.tap(find.text('Jump to Top')); + await tester.pump(); + expect(position.pixels, 0.0); + }); +} diff --git a/packages/two_dimensional_scrollables/example/web/favicon.png b/packages/two_dimensional_scrollables/example/web/favicon.png new file mode 100644 index 00000000000..8aaa46ac1ae Binary files /dev/null and b/packages/two_dimensional_scrollables/example/web/favicon.png differ diff --git a/packages/two_dimensional_scrollables/example/web/icons/Icon-192.png b/packages/two_dimensional_scrollables/example/web/icons/Icon-192.png new file mode 100644 index 00000000000..b749bfef074 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/web/icons/Icon-192.png differ diff --git a/packages/two_dimensional_scrollables/example/web/icons/Icon-512.png b/packages/two_dimensional_scrollables/example/web/icons/Icon-512.png new file mode 100644 index 00000000000..88cfd48dff1 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/web/icons/Icon-512.png differ diff --git a/packages/two_dimensional_scrollables/example/web/icons/Icon-maskable-192.png b/packages/two_dimensional_scrollables/example/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000000..eb9b4d76e52 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/two_dimensional_scrollables/example/web/icons/Icon-maskable-512.png b/packages/two_dimensional_scrollables/example/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000000..d69c56691fb Binary files /dev/null and b/packages/two_dimensional_scrollables/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/two_dimensional_scrollables/example/web/index.html b/packages/two_dimensional_scrollables/example/web/index.html new file mode 100644 index 00000000000..3574a82d337 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/web/index.html @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + example + + + + + + + + + + diff --git a/packages/two_dimensional_scrollables/example/web/manifest.json b/packages/two_dimensional_scrollables/example/web/manifest.json new file mode 100644 index 00000000000..bcbd9930fd4 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": ""A new Flutter project."", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/two_dimensional_scrollables/example/windows/.gitignore b/packages/two_dimensional_scrollables/example/windows/.gitignore new file mode 100644 index 00000000000..d492d0d98c8 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/packages/two_dimensional_scrollables/example/windows/CMakeLists.txt b/packages/two_dimensional_scrollables/example/windows/CMakeLists.txt new file mode 100644 index 00000000000..c09389c56bf --- /dev/null +++ b/packages/two_dimensional_scrollables/example/windows/CMakeLists.txt @@ -0,0 +1,102 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/packages/two_dimensional_scrollables/example/windows/flutter/CMakeLists.txt b/packages/two_dimensional_scrollables/example/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000000..930d2071a32 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/packages/two_dimensional_scrollables/example/windows/flutter/generated_plugins.cmake b/packages/two_dimensional_scrollables/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000000..b93c4c30c16 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/two_dimensional_scrollables/example/windows/runner/CMakeLists.txt b/packages/two_dimensional_scrollables/example/windows/runner/CMakeLists.txt new file mode 100644 index 00000000000..394917c053a --- /dev/null +++ b/packages/two_dimensional_scrollables/example/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/two_dimensional_scrollables/example/windows/runner/Runner.rc b/packages/two_dimensional_scrollables/example/windows/runner/Runner.rc new file mode 100644 index 00000000000..aecaa2b505d --- /dev/null +++ b/packages/two_dimensional_scrollables/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "example.exe" "\0" + VALUE "ProductName", "example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/packages/two_dimensional_scrollables/example/windows/runner/flutter_window.cpp b/packages/two_dimensional_scrollables/example/windows/runner/flutter_window.cpp new file mode 100644 index 00000000000..081067d2a5f --- /dev/null +++ b/packages/two_dimensional_scrollables/example/windows/runner/flutter_window.cpp @@ -0,0 +1,73 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { this->Show(); }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/packages/two_dimensional_scrollables/example/windows/runner/flutter_window.h b/packages/two_dimensional_scrollables/example/windows/runner/flutter_window.h new file mode 100644 index 00000000000..f1fc669093d --- /dev/null +++ b/packages/two_dimensional_scrollables/example/windows/runner/flutter_window.h @@ -0,0 +1,37 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/two_dimensional_scrollables/example/windows/runner/main.cpp b/packages/two_dimensional_scrollables/example/windows/runner/main.cpp new file mode 100644 index 00000000000..0980599366c --- /dev/null +++ b/packages/two_dimensional_scrollables/example/windows/runner/main.cpp @@ -0,0 +1,46 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t* command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/packages/two_dimensional_scrollables/example/windows/runner/resource.h b/packages/two_dimensional_scrollables/example/windows/runner/resource.h new file mode 100644 index 00000000000..d5d958dc425 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/packages/two_dimensional_scrollables/example/windows/runner/resources/app_icon.ico b/packages/two_dimensional_scrollables/example/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000000..c04e20caf63 Binary files /dev/null and b/packages/two_dimensional_scrollables/example/windows/runner/resources/app_icon.ico differ diff --git a/packages/two_dimensional_scrollables/example/windows/runner/runner.exe.manifest b/packages/two_dimensional_scrollables/example/windows/runner/runner.exe.manifest new file mode 100644 index 00000000000..a42ea7687cb --- /dev/null +++ b/packages/two_dimensional_scrollables/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/packages/two_dimensional_scrollables/example/windows/runner/utils.cpp b/packages/two_dimensional_scrollables/example/windows/runner/utils.cpp new file mode 100644 index 00000000000..8269e813bba --- /dev/null +++ b/packages/two_dimensional_scrollables/example/windows/runner/utils.cpp @@ -0,0 +1,69 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE* unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = + ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, + nullptr, 0, nullptr, nullptr) - + 1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, input_length, + utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/packages/two_dimensional_scrollables/example/windows/runner/utils.h b/packages/two_dimensional_scrollables/example/windows/runner/utils.h new file mode 100644 index 00000000000..bd81e1e0233 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/windows/runner/utils.h @@ -0,0 +1,23 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/packages/two_dimensional_scrollables/example/windows/runner/win32_window.cpp b/packages/two_dimensional_scrollables/example/windows/runner/win32_window.cpp new file mode 100644 index 00000000000..98c69c665c7 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/windows/runner/win32_window.cpp @@ -0,0 +1,284 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: +/// https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = + L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { ++g_active_window_count; } + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { return ShowWindow(window_handle_, SW_SHOWNORMAL); } + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { return window_handle_; } + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = + RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, RRF_RT_REG_DWORD, nullptr, + &light_mode, &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/packages/two_dimensional_scrollables/example/windows/runner/win32_window.h b/packages/two_dimensional_scrollables/example/windows/runner/win32_window.h new file mode 100644 index 00000000000..6b5a657aad0 --- /dev/null +++ b/packages/two_dimensional_scrollables/example/windows/runner/win32_window.h @@ -0,0 +1,104 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/two_dimensional_scrollables/lib/src/table_view/table.dart b/packages/two_dimensional_scrollables/lib/src/table_view/table.dart new file mode 100644 index 00000000000..4b1b5a99cd3 --- /dev/null +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table.dart @@ -0,0 +1,1113 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:collection'; +import 'dart:math' as math; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +import 'table_cell.dart'; +import 'table_delegate.dart'; +import 'table_span.dart'; + +/// A widget that displays a table, which can scroll in horizontal and vertical +/// directions. +/// +/// A table consists of rows and columns. Rows fill the horizontal space of +/// the table, while columns fill it vertically. If there is not enough space +/// available to display all the rows at the same time, the table will scroll +/// vertically. If there is not enough space for all the columns, it will +/// scroll horizontally. +/// +/// Each child [Widget] can belong to exactly one row and one column as +/// represented by its [TableVicinity]. The table supports lazy rendering and +/// will only instantiate those cells that are currently visible in the table's +/// viewport and those that extend into the [cacheExtent]. +/// +/// The layout of the table (e.g. how many rows/columns there are and their +/// extents) as well as the content of the individual cells is defined by +/// the provided [delegate], a subclass of [TwoDimensionalChildDelegate] with +/// the [TableCellDelegateMixin]. The [TableView.builder] and [TableView.list] +/// constructors create their own delegate. +/// +/// This example shows a TableView of 100 children, all sized 100 by 100 +/// pixels with a few [TableSpanDecoration]s like background colors and borders. +/// The `builder` constructor is called on demand for the cells that are visible +/// in the TableView. +/// +/// ```dart +/// TableView.builder( +/// cellBuilder: (BuildContext context, TableVicinity vicinity) { +/// return Center( +/// child: Text('Cell ${vicinity.column} : ${vicinity.row}'), +/// ); +/// }, +/// columnCount: 10, +/// columnBuilder: (int column) { +/// return TableSpan( +/// extent: FixedTableSpanExtent(100), +/// foregroundDecoration: TableSpanDecoration( +/// border: TableSpanBorder( +/// trailing: BorderSide( +/// color: Colors.black, +/// width: 2, +/// style: BorderStyle.solid, +/// ), +/// ), +/// ), +/// ); +/// }, +/// rowCount: 10, +/// rowBuilder: (int row) { +/// return TableSpan( +/// extent: FixedTableSpanExtent(100), +/// backgroundDecoration: TableSpanDecoration( +/// color: row.isEven? Colors.blueAccent[100] : Colors.white, +/// ), +/// ); +/// }, +/// ); +/// ``` +/// +/// See also: +/// +/// * [TableSpan], describes the configuration for a row or column in the +/// TableView. +/// * [TwoDimensionalScrollView], the super class that is extended by TableView. +/// * [GridView], another scrolling widget that can be used to create tables +/// that scroll in one dimension. +class TableView extends TwoDimensionalScrollView { + /// Creates a [TableView] that scrolls in both dimensions. + /// + /// A non-null [delegate] must be provided. + const TableView({ + super.key, + super.primary, + super.mainAxis, + super.horizontalDetails, + super.verticalDetails, + super.cacheExtent, + required TableCellDelegateMixin super.delegate, + super.diagonalDragBehavior = DiagonalDragBehavior.none, + super.dragStartBehavior, + super.keyboardDismissBehavior, + super.clipBehavior, + }); + + /// Creates a [TableView] of widgets that are created on demand. + /// + /// This constructor is appropriate for table views with a large + /// number of cells because the [cellbuilder] is called only for those + /// cells that are actually visible. + /// + /// This constructor generates a [TableCellBuilderDelegate] for building + /// children on demand using the required [cellBuilder], + /// [columnBuilder], and [rowBuilder]. + TableView.builder({ + super.key, + super.primary, + super.mainAxis, + super.horizontalDetails, + super.verticalDetails, + super.cacheExtent, + super.diagonalDragBehavior = DiagonalDragBehavior.none, + super.dragStartBehavior, + super.keyboardDismissBehavior, + super.clipBehavior, + int pinnedRowCount = 0, + int pinnedColumnCount = 0, + required int columnCount, + required int rowCount, + required TableSpanBuilder columnBuilder, + required TableSpanBuilder rowBuilder, + required TableViewCellBuilder cellBuilder, + }) : assert(pinnedRowCount >= 0), + assert(rowCount >= 0), + assert(rowCount >= pinnedRowCount), + assert(columnCount >= 0), + assert(pinnedColumnCount >= 0), + assert(columnCount >= pinnedColumnCount), + super( + delegate: TableCellBuilderDelegate( + columnCount: columnCount, + rowCount: rowCount, + pinnedColumnCount: pinnedColumnCount, + pinnedRowCount: pinnedRowCount, + cellBuilder: cellBuilder, + columnBuilder: columnBuilder, + rowBuilder: rowBuilder, + ), + ); + + /// Creates a [TableView] from an explicit two dimensional array of children. + /// + /// This constructor is appropriate for list views with a small number of + /// children because constructing the [List] requires doing work for every + /// child that could possibly be displayed in the list view instead of just + /// those children that are actually visible. + /// + /// The [children] are accessed for each [TableVicinity.column] and + /// [TableVicinity.row] of the [TwoDimensionalViewport] as + /// `children[vicinity.column][vicinity.row]`. + TableView.list({ + super.key, + super.primary, + super.mainAxis, + super.horizontalDetails, + super.verticalDetails, + super.cacheExtent, + super.diagonalDragBehavior = DiagonalDragBehavior.none, + super.dragStartBehavior, + super.keyboardDismissBehavior, + super.clipBehavior, + int pinnedRowCount = 0, + int pinnedColumnCount = 0, + required TableSpanBuilder columnBuilder, + required TableSpanBuilder rowBuilder, + List> cells = const >[], + }) : assert(pinnedRowCount >= 0), + assert(pinnedColumnCount >= 0), + super( + delegate: TableCellListDelegate( + pinnedColumnCount: pinnedColumnCount, + pinnedRowCount: pinnedRowCount, + cells: cells, + columnBuilder: columnBuilder, + rowBuilder: rowBuilder, + ), + ); + + @override + TableViewport buildViewport( + BuildContext context, + ViewportOffset verticalOffset, + ViewportOffset horizontalOffset, + ) { + return TableViewport( + verticalOffset: verticalOffset, + verticalAxisDirection: verticalDetails.direction, + horizontalOffset: horizontalOffset, + horizontalAxisDirection: horizontalDetails.direction, + delegate: delegate as TableCellDelegateMixin, + mainAxis: mainAxis, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + ); + } +} + +/// A widget through which a portion of a Table of [Widget] children are viewed, +/// typically in combination with a [TableView]. +class TableViewport extends TwoDimensionalViewport { + /// Creates a viewport for [Widget]s that extend and scroll in both + /// horizontal and vertical dimensions. + const TableViewport({ + super.key, + required super.verticalOffset, + required super.verticalAxisDirection, + required super.horizontalOffset, + required super.horizontalAxisDirection, + required TableCellDelegateMixin super.delegate, + required super.mainAxis, + super.cacheExtent, + super.clipBehavior, + }); + + @override + RenderTwoDimensionalViewport createRenderObject(BuildContext context) { + return RenderTableViewport( + horizontalOffset: horizontalOffset, + horizontalAxisDirection: horizontalAxisDirection, + verticalOffset: verticalOffset, + verticalAxisDirection: verticalAxisDirection, + mainAxis: mainAxis, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + delegate: delegate as TableCellDelegateMixin, + childManager: context as TwoDimensionalChildManager, + ); + } + + @override + void updateRenderObject( + BuildContext context, + RenderTableViewport renderObject, + ) { + renderObject + ..horizontalOffset = horizontalOffset + ..horizontalAxisDirection = horizontalAxisDirection + ..verticalOffset = verticalOffset + ..verticalAxisDirection = verticalAxisDirection + ..mainAxis = mainAxis + ..cacheExtent = cacheExtent + ..clipBehavior = clipBehavior + ..delegate = delegate as TableCellDelegateMixin; + } +} + +/// A render object for viewing [RenderBox]es in a table format that extends in +/// both the horizontal and vertical dimensions. +/// +/// [RenderTableViewport] is the visual workhorse of the [TableView]. It +/// displays a subset of its children according to its own dimensions and the +/// given [verticalOffset] and [horizontalOffset]. As the offset varies, +/// different children are visible through the viewport. +class RenderTableViewport extends RenderTwoDimensionalViewport { + /// Creates a viewport for [RenderBox] objects in a table format of rows and + /// columns. + RenderTableViewport({ + required super.horizontalOffset, + required super.horizontalAxisDirection, + required super.verticalOffset, + required super.verticalAxisDirection, + required TableCellDelegateMixin super.delegate, + required super.mainAxis, + required super.childManager, + super.cacheExtent, + super.clipBehavior, + }); + + @override + TableCellDelegateMixin get delegate => + super.delegate as TableCellDelegateMixin; + @override + set delegate(TableCellDelegateMixin value) { + super.delegate = value; + } + + // Cached Table metrics + Map _columnMetrics = {}; + Map _rowMetrics = {}; + int? _firstNonPinnedRow; + int? _firstNonPinnedColumn; + int? _lastNonPinnedRow; + int? _lastNonPinnedColumn; + + TableVicinity? get _firstNonPinnedCell { + if (_firstNonPinnedRow == null || _firstNonPinnedColumn == null) { + return null; + } + return TableVicinity( + column: _firstNonPinnedColumn!, + row: _firstNonPinnedRow!, + ); + } + + TableVicinity? get _lastNonPinnedCell { + if (_lastNonPinnedRow == null || _lastNonPinnedColumn == null) { + return null; + } + return TableVicinity( + column: _lastNonPinnedColumn!, + row: _lastNonPinnedRow!, + ); + } + + int? get _lastPinnedRow => + delegate.pinnedRowCount > 0 ? delegate.pinnedRowCount - 1 : null; + int? get _lastPinnedColumn => + delegate.pinnedColumnCount > 0 ? delegate.pinnedColumnCount - 1 : null; + + double get _pinnedRowsExtent => _lastPinnedRow != null + ? _rowMetrics[_lastPinnedRow]!.trailingOffset + : 0.0; + double get _pinnedColumnsExtent => _lastPinnedColumn != null + ? _columnMetrics[_lastPinnedColumn]!.trailingOffset + : 0.0; + + @override + TableViewParentData parentDataOf(RenderBox child) => + child.parentData! as TableViewParentData; + + @override + void setupParentData(RenderBox child) { + if (child.parentData is! TableViewParentData) { + child.parentData = TableViewParentData(); + } + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + RenderBox? cell = firstChild; + while (cell != null) { + final TableViewParentData cellParentData = parentDataOf(cell); + if (!cellParentData.isVisible) { + // This cell is not visible, so it cannot be hit. + cell = childAfter(cell); + continue; + } + final Rect cellRect = cellParentData.paintOffset! & cell.size; + if (cellRect.contains(position)) { + result.addWithPaintOffset( + offset: cellParentData.paintOffset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + assert(transformed == position - cellParentData.paintOffset!); + return cell!.hitTest(result, position: transformed); + }, + ); + switch (mainAxis) { + case Axis.vertical: + // Row major order, rows go first. + result.add( + HitTestEntry(_rowMetrics[cellParentData.tableVicinity.row]!), + ); + result.add( + HitTestEntry( + _columnMetrics[cellParentData.tableVicinity.column]!), + ); + break; + case Axis.horizontal: + // Column major order, columns go first. + result.add( + HitTestEntry( + _columnMetrics[cellParentData.tableVicinity.column]!), + ); + result.add( + HitTestEntry(_rowMetrics[cellParentData.tableVicinity.row]!), + ); + break; + } + return true; + } + cell = childAfter(cell); + } + return false; + } + + // Updates the cached metrics for the table. + // + // Will iterate through all columns and rows to define the layout pattern of + // the cells of the table. + // + // TODO(Piinks): Add back infinite separately for easier review, https://github.com/flutter/flutter/issues/131226 + // Only relevant when the number of rows and columns is finite + void _updateAllMetrics() { + assert(needsDelegateRebuild || didResize); + + _firstNonPinnedColumn = null; + _lastNonPinnedColumn = null; + double startOfRegularColumn = 0; + double startOfPinnedColumn = 0; + + final Map newColumnMetrics = {}; + for (int column = 0; column < delegate.columnCount; column++) { + final bool isPinned = column < delegate.pinnedColumnCount; + final double leadingOffset = + isPinned ? startOfPinnedColumn : startOfRegularColumn; + _Span? span = _columnMetrics.remove(column); + assert(needsDelegateRebuild || span != null); + final TableSpan configuration = needsDelegateRebuild + ? delegate.buildColumn(column) + : span!.configuration; + span ??= _Span(); + span.update( + isPinned: isPinned, + configuration: configuration, + leadingOffset: leadingOffset, + extent: configuration.extent.calculateExtent( + TableSpanExtentDelegate( + viewportExtent: viewportDimension.width, + precedingExtent: leadingOffset, + ), + ), + ); + newColumnMetrics[column] = span; + if (!isPinned) { + if (span.trailingOffset >= horizontalOffset.pixels && + _firstNonPinnedColumn == null) { + _firstNonPinnedColumn = column; + } + final double targetColumnPixel = cacheExtent + + horizontalOffset.pixels + + viewportDimension.width - + startOfPinnedColumn; + if (span.trailingOffset >= targetColumnPixel && + _lastNonPinnedColumn == null) { + _lastNonPinnedColumn = column; + } + startOfRegularColumn = span.trailingOffset; + } else { + startOfPinnedColumn = span.trailingOffset; + } + } + assert(newColumnMetrics.length >= delegate.pinnedColumnCount); + for (final _Span span in _columnMetrics.values) { + span.dispose(); + } + _columnMetrics = newColumnMetrics; + + _firstNonPinnedRow = null; + _lastNonPinnedRow = null; + double startOfRegularRow = 0; + double startOfPinnedRow = 0; + + final Map newRowMetrics = {}; + for (int row = 0; row < delegate.rowCount; row++) { + final bool isPinned = row < delegate.pinnedRowCount; + final double leadingOffset = + isPinned ? startOfPinnedRow : startOfRegularRow; + _Span? span = _rowMetrics.remove(row); + assert(needsDelegateRebuild || span != null); + final TableSpan configuration = + needsDelegateRebuild ? delegate.buildRow(row) : span!.configuration; + span ??= _Span(); + span.update( + isPinned: isPinned, + configuration: configuration, + leadingOffset: leadingOffset, + extent: configuration.extent.calculateExtent( + TableSpanExtentDelegate( + viewportExtent: viewportDimension.height, + precedingExtent: leadingOffset, + ), + ), + ); + newRowMetrics[row] = span; + if (!isPinned) { + if (span.trailingOffset >= verticalOffset.pixels && + _firstNonPinnedRow == null) { + _firstNonPinnedRow = row; + } + final double targetRowPixel = cacheExtent + + verticalOffset.pixels + + viewportDimension.height - + startOfPinnedRow; + if (span.trailingOffset >= targetRowPixel && + _lastNonPinnedRow == null) { + _lastNonPinnedRow = row; + } + startOfRegularRow = span.trailingOffset; + } else { + startOfPinnedRow = span.trailingOffset; + } + } + assert(newRowMetrics.length >= delegate.pinnedRowCount); + for (final _Span span in _rowMetrics.values) { + span.dispose(); + } + _rowMetrics = newRowMetrics; + + final double maxVerticalScrollExtent; + if (_rowMetrics.length <= delegate.pinnedRowCount) { + assert(_firstNonPinnedRow == null && _lastNonPinnedRow == null); + maxVerticalScrollExtent = 0.0; + } else { + final int lastRow = _rowMetrics.length - 1; + if (_firstNonPinnedRow != null) { + _lastNonPinnedRow ??= lastRow; + } + maxVerticalScrollExtent = math.max( + 0.0, + _rowMetrics[lastRow]!.trailingOffset - + viewportDimension.height + + startOfPinnedRow, + ); + } + + final double maxHorizontalScrollExtent; + if (_columnMetrics.length <= delegate.pinnedColumnCount) { + assert(_firstNonPinnedColumn == null && _lastNonPinnedColumn == null); + maxHorizontalScrollExtent = 0.0; + } else { + final int lastColumn = _columnMetrics.length - 1; + if (_firstNonPinnedColumn != null) { + _lastNonPinnedColumn ??= lastColumn; + } + maxHorizontalScrollExtent = math.max( + 0.0, + _columnMetrics[lastColumn]!.trailingOffset - + viewportDimension.width + + startOfPinnedColumn, + ); + } + + final bool acceptedDimension = horizontalOffset.applyContentDimensions( + 0.0, maxHorizontalScrollExtent) && + verticalOffset.applyContentDimensions(0.0, maxVerticalScrollExtent); + if (!acceptedDimension) { + _updateFirstAndLastVisibleCell(); + } + } + + // Uses the cached metrics to update the currently visible cells + // + // TODO(Piinks): Add back infinite separately for easier review, https://github.com/flutter/flutter/issues/131226 + // Only relevant when the number of rows and columns is finite + void _updateFirstAndLastVisibleCell() { + _firstNonPinnedColumn = null; + _lastNonPinnedColumn = null; + final double targetColumnPixel = cacheExtent + + horizontalOffset.pixels + + viewportDimension.width - + _pinnedColumnsExtent; + for (int column = 0; column < _columnMetrics.length; column++) { + if (_columnMetrics[column]!.isPinned) { + continue; + } + final double endOfColumn = _columnMetrics[column]!.trailingOffset; + if (endOfColumn >= horizontalOffset.pixels && + _firstNonPinnedColumn == null) { + _firstNonPinnedColumn = column; + } + if (endOfColumn >= targetColumnPixel && _lastNonPinnedColumn == null) { + _lastNonPinnedColumn = column; + break; + } + } + if (_firstNonPinnedColumn != null) { + _lastNonPinnedColumn ??= _columnMetrics.length - 1; + } + + _firstNonPinnedRow = null; + _lastNonPinnedRow = null; + final double targetRowPixel = cacheExtent + + verticalOffset.pixels + + viewportDimension.height - + _pinnedRowsExtent; + for (int row = 0; row < _rowMetrics.length; row++) { + if (_rowMetrics[row]!.isPinned) { + continue; + } + final double endOfRow = _rowMetrics[row]!.trailingOffset; + if (endOfRow >= verticalOffset.pixels && _firstNonPinnedRow == null) { + _firstNonPinnedRow = row; + } + if (endOfRow >= targetRowPixel && _lastNonPinnedRow == null) { + _lastNonPinnedRow = row; + break; + } + } + if (_firstNonPinnedRow != null) { + _lastNonPinnedRow ??= _rowMetrics.length - 1; + } + } + + @override + void layoutChildSequence() { + if (needsDelegateRebuild || didResize) { + // Recomputes the table metrics, invalidates any cached information. + _updateAllMetrics(); + } else { + // Updates the visible cells based on cached table metrics. + _updateFirstAndLastVisibleCell(); + } + + if (_firstNonPinnedCell == null && + _lastPinnedRow == null && + _lastPinnedColumn == null) { + assert(_lastNonPinnedCell == null); + return; + } + + final double? offsetIntoColumn = _firstNonPinnedColumn != null + ? horizontalOffset.pixels - + _columnMetrics[_firstNonPinnedColumn]!.leadingOffset - + _pinnedColumnsExtent + : null; + final double? offsetIntoRow = _firstNonPinnedRow != null + ? verticalOffset.pixels - + _rowMetrics[_firstNonPinnedRow]!.leadingOffset - + _pinnedRowsExtent + : null; + + if (_lastPinnedRow != null && _lastPinnedColumn != null) { + // Layout cells that are contained in both pinned rows and columns + _layoutCells( + start: const TableVicinity(column: 0, row: 0), + end: TableVicinity(column: _lastPinnedColumn!, row: _lastPinnedRow!), + offset: Offset.zero, + ); + } + + if (_lastPinnedRow != null && _firstNonPinnedColumn != null) { + // Layout cells of pinned rows - those that do not intersect with pinned + // columns above + assert(_lastNonPinnedColumn != null); + assert(offsetIntoColumn != null); + _layoutCells( + start: TableVicinity(column: _firstNonPinnedColumn!, row: 0), + end: TableVicinity(column: _lastNonPinnedColumn!, row: _lastPinnedRow!), + offset: Offset(offsetIntoColumn!, 0), + ); + } + if (_lastPinnedColumn != null && _firstNonPinnedRow != null) { + // Layout cells of pinned columns - those that do not intersect with + // pinned rows above + assert(_lastNonPinnedRow != null); + assert(offsetIntoRow != null); + _layoutCells( + start: TableVicinity(column: 0, row: _firstNonPinnedRow!), + end: TableVicinity(column: _lastPinnedColumn!, row: _lastNonPinnedRow!), + offset: Offset(0, offsetIntoRow!), + ); + } + if (_firstNonPinnedCell != null) { + // Layout all other cells. + assert(_lastNonPinnedCell != null); + assert(offsetIntoColumn != null); + assert(offsetIntoRow != null); + _layoutCells( + start: _firstNonPinnedCell!, + end: _lastNonPinnedCell!, + offset: Offset(offsetIntoColumn!, offsetIntoRow!), + ); + } + } + + void _layoutCells({ + required TableVicinity start, + required TableVicinity end, + required Offset offset, + }) { + // TODO(Piinks): Assert here or somewhere else merged cells cannot span + // pinned and unpinned cells (for merged cell follow-up), https://github.com/flutter/flutter/issues/131224 + double yPaintOffset = -offset.dy; + for (int row = start.row; row <= end.row; row += 1) { + double xPaintOffset = -offset.dx; + final double rowHeight = _rowMetrics[row]!.extent; + for (int column = start.column; column <= end.column; column += 1) { + final double columnWidth = _columnMetrics[column]!.extent; + + final TableVicinity vicinity = TableVicinity(column: column, row: row); + // TODO(Piinks): Add back merged cells, https://github.com/flutter/flutter/issues/131224 + + final RenderBox? cell = buildOrObtainChildFor(vicinity); + + if (cell != null) { + final TableViewParentData cellParentData = parentDataOf(cell); + + final BoxConstraints cellConstraints = BoxConstraints.tightFor( + width: columnWidth, + height: rowHeight, + ); + cell.layout(cellConstraints); + cellParentData.layoutOffset = Offset(xPaintOffset, yPaintOffset); + } + xPaintOffset += columnWidth; + } + yPaintOffset += rowHeight; + } + } + + final LayerHandle _clipPinnedRowsHandle = + LayerHandle(); + final LayerHandle _clipPinnedColumnsHandle = + LayerHandle(); + final LayerHandle _clipCellsHandle = + LayerHandle(); + + @override + void paint(PaintingContext context, Offset offset) { + if (_firstNonPinnedCell == null && + _lastPinnedRow == null && + _lastPinnedColumn == null) { + assert(_lastNonPinnedCell == null); + return; + } + + // Subclasses of RenderTwoDimensionalViewport will typically use + // firstChild to traverse children in a standard paint order that + // follows row or column major ordering. Here is slightly different + // as we break the cells up into 4 main paint passes to clip for overlap. + + if (_firstNonPinnedCell != null) { + // Paint all visible un-pinned cells + assert(_lastNonPinnedCell != null); + _clipCellsHandle.layer = context.pushClipRect( + needsCompositing, + offset, + Rect.fromLTWH( + _pinnedColumnsExtent, + _pinnedRowsExtent, + viewportDimension.width - _pinnedColumnsExtent, + viewportDimension.height - _pinnedRowsExtent, + ), + (PaintingContext context, Offset offset) { + _paintCells( + context: context, + offset: offset, + leading: _firstNonPinnedCell!, + trailing: _lastNonPinnedCell!, + ); + }, + clipBehavior: clipBehavior, + oldLayer: _clipCellsHandle.layer, + ); + } else { + _clipCellsHandle.layer = null; + } + + if (_lastPinnedColumn != null && _firstNonPinnedRow != null) { + // Paint all visible pinned column cells that do not intersect with pinned + // row cells. + _clipPinnedColumnsHandle.layer = context.pushClipRect( + needsCompositing, + offset, + Rect.fromLTWH( + 0.0, + _pinnedRowsExtent, + _pinnedColumnsExtent, + viewportDimension.height - _pinnedRowsExtent, + ), + (PaintingContext context, Offset offset) { + _paintCells( + context: context, + offset: offset, + leading: TableVicinity(column: 0, row: _firstNonPinnedRow!), + trailing: TableVicinity( + column: _lastPinnedColumn!, row: _lastNonPinnedRow!), + ); + }, + clipBehavior: clipBehavior, + oldLayer: _clipPinnedColumnsHandle.layer, + ); + } else { + _clipPinnedColumnsHandle.layer = null; + } + + if (_lastPinnedRow != null && _firstNonPinnedColumn != null) { + // Paint all visible pinned row cells that do not intersect with pinned + // column cells. + _clipPinnedRowsHandle.layer = context.pushClipRect( + needsCompositing, + offset, + Rect.fromLTWH( + _pinnedColumnsExtent, + 0.0, + viewportDimension.width - _pinnedColumnsExtent, + _pinnedRowsExtent, + ), + (PaintingContext context, Offset offset) { + _paintCells( + context: context, + offset: offset, + leading: TableVicinity(column: _firstNonPinnedColumn!, row: 0), + trailing: TableVicinity( + column: _lastNonPinnedColumn!, row: _lastPinnedRow!), + ); + }, + clipBehavior: clipBehavior, + oldLayer: _clipPinnedRowsHandle.layer, + ); + } else { + _clipPinnedRowsHandle.layer = null; + } + + if (_lastPinnedRow != null && _lastPinnedColumn != null) { + // Paint remaining visible pinned cells that represent the intersection of + // both pinned rows and columns. + _paintCells( + context: context, + offset: offset, + leading: const TableVicinity(column: 0, row: 0), + trailing: + TableVicinity(column: _lastPinnedColumn!, row: _lastPinnedRow!), + ); + } + } + + void _paintCells({ + required PaintingContext context, + required TableVicinity leading, + required TableVicinity trailing, + required Offset offset, + }) { + // Column decorations + final LinkedHashMap foregroundColumns = + LinkedHashMap(); + final LinkedHashMap backgroundColumns = + LinkedHashMap(); + + for (int column = leading.column; column <= trailing.column; column++) { + final _Span span = _columnMetrics[column]!; + if (span.configuration.backgroundDecoration != null || + span.configuration.foregroundDecoration != null) { + final RenderBox leadingCell = getChildFor( + TableVicinity(column: column, row: leading.row), + )!; + final RenderBox trailingCell = getChildFor( + TableVicinity(column: column, row: trailing.row), + )!; + + final Rect rect = Rect.fromPoints( + parentDataOf(leadingCell).paintOffset! + offset, + parentDataOf(trailingCell).paintOffset! + + Offset(trailingCell.size.width, trailingCell.size.height) + + offset, + ); + + if (span.configuration.backgroundDecoration != null) { + backgroundColumns[rect] = span.configuration.backgroundDecoration!; + } + if (span.configuration.foregroundDecoration != null) { + foregroundColumns[rect] = span.configuration.foregroundDecoration!; + } + } + } + + // Row decorations + final LinkedHashMap foregroundRows = + LinkedHashMap(); + final LinkedHashMap backgroundRows = + LinkedHashMap(); + + for (int row = leading.row; row <= trailing.row; row++) { + final _Span span = _rowMetrics[row]!; + if (span.configuration.backgroundDecoration != null || + span.configuration.foregroundDecoration != null) { + final RenderBox leadingCell = getChildFor( + TableVicinity(column: leading.column, row: row), + )!; + final RenderBox trailingCell = getChildFor( + TableVicinity(column: trailing.column, row: row), + )!; + + final Rect rect = Rect.fromPoints( + parentDataOf(leadingCell).paintOffset! + offset, + parentDataOf(trailingCell).paintOffset! + + Offset(trailingCell.size.width, trailingCell.size.height) + + offset, + ); + if (span.configuration.backgroundDecoration != null) { + backgroundRows[rect] = span.configuration.backgroundDecoration!; + } + if (span.configuration.foregroundDecoration != null) { + foregroundRows[rect] = span.configuration.foregroundDecoration!; + } + } + } + + // Get to painting. + // Painting is done in row or column major ordering according to the main + // axis, with background decorations first, cells next, and foreground + // decorations last. + + // Background decorations + switch (mainAxis) { + // Default, row major order. Rows go first. + case Axis.vertical: + backgroundRows.forEach((Rect rect, TableSpanDecoration decoration) { + final TableSpanDecorationPaintDetails paintingDetails = + TableSpanDecorationPaintDetails( + canvas: context.canvas, + rect: rect, + axisDirection: horizontalAxisDirection, + ); + decoration.paint(paintingDetails); + }); + backgroundColumns.forEach((Rect rect, TableSpanDecoration decoration) { + final TableSpanDecorationPaintDetails paintingDetails = + TableSpanDecorationPaintDetails( + canvas: context.canvas, + rect: rect, + axisDirection: verticalAxisDirection, + ); + decoration.paint(paintingDetails); + }); + break; + // Column major order. Columns go first. + case Axis.horizontal: + backgroundColumns.forEach((Rect rect, TableSpanDecoration decoration) { + final TableSpanDecorationPaintDetails paintingDetails = + TableSpanDecorationPaintDetails( + canvas: context.canvas, + rect: rect, + axisDirection: verticalAxisDirection, + ); + decoration.paint(paintingDetails); + }); + backgroundRows.forEach((Rect rect, TableSpanDecoration decoration) { + final TableSpanDecorationPaintDetails paintingDetails = + TableSpanDecorationPaintDetails( + canvas: context.canvas, + rect: rect, + axisDirection: horizontalAxisDirection, + ); + decoration.paint(paintingDetails); + }); + } + + // Cells + for (int column = leading.column; column <= trailing.column; column++) { + for (int row = leading.row; row <= trailing.row; row++) { + final RenderBox cell = getChildFor( + TableVicinity(column: column, row: row), + )!; + final TableViewParentData cellParentData = parentDataOf(cell); + if (cellParentData.isVisible) { + context.paintChild(cell, offset + cellParentData.paintOffset!); + } + } + } + + // Foreground decorations + switch (mainAxis) { + // Default, row major order. Rows go first. + case Axis.vertical: + foregroundRows.forEach((Rect rect, TableSpanDecoration decoration) { + final TableSpanDecorationPaintDetails paintingDetails = + TableSpanDecorationPaintDetails( + canvas: context.canvas, + rect: rect, + axisDirection: horizontalAxisDirection, + ); + decoration.paint(paintingDetails); + }); + foregroundColumns.forEach((Rect rect, TableSpanDecoration decoration) { + final TableSpanDecorationPaintDetails paintingDetails = + TableSpanDecorationPaintDetails( + canvas: context.canvas, + rect: rect, + axisDirection: verticalAxisDirection, + ); + decoration.paint(paintingDetails); + }); + break; + // Column major order. Columns go first. + case Axis.horizontal: + foregroundColumns.forEach((Rect rect, TableSpanDecoration decoration) { + final TableSpanDecorationPaintDetails paintingDetails = + TableSpanDecorationPaintDetails( + canvas: context.canvas, + rect: rect, + axisDirection: verticalAxisDirection, + ); + decoration.paint(paintingDetails); + }); + foregroundRows.forEach((Rect rect, TableSpanDecoration decoration) { + final TableSpanDecorationPaintDetails paintingDetails = + TableSpanDecorationPaintDetails( + canvas: context.canvas, + rect: rect, + axisDirection: horizontalAxisDirection, + ); + decoration.paint(paintingDetails); + }); + } + } + + @override + void dispose() { + _clipPinnedRowsHandle.layer = null; + _clipPinnedColumnsHandle.layer = null; + _clipCellsHandle.layer = null; + super.dispose(); + } +} + +class _Span + with Diagnosticable + implements HitTestTarget, MouseTrackerAnnotation { + double get leadingOffset => _leadingOffset; + late double _leadingOffset; + + double get extent => _extent; + late double _extent; + + TableSpan get configuration => _configuration!; + TableSpan? _configuration; + + bool get isPinned => _isPinned; + late bool _isPinned; + + double get trailingOffset => leadingOffset + extent; + + // ---- Span Management ---- + + void update({ + required TableSpan configuration, + required double leadingOffset, + required double extent, + required bool isPinned, + }) { + _leadingOffset = leadingOffset; + _extent = extent; + _isPinned = isPinned; + if (configuration == _configuration) { + return; + } + _configuration = configuration; + // Only sync recognizers if they are in use already. + if (_recognizers != null) { + _syncRecognizers(); + } + } + + void dispose() { + _disposeRecognizers(); + } + + // ---- Recognizers management ---- + + Map? _recognizers; + + void _syncRecognizers() { + if (configuration.recognizerFactories.isEmpty) { + _disposeRecognizers(); + return; + } + final Map newRecognizers = + {}; + for (final Type type in configuration.recognizerFactories.keys) { + assert(!newRecognizers.containsKey(type)); + newRecognizers[type] = _recognizers?.remove(type) ?? + configuration.recognizerFactories[type]!.constructor(); + assert( + newRecognizers[type].runtimeType == type, + 'GestureRecognizerFactory of type $type created a GestureRecognizer of ' + 'type ${newRecognizers[type].runtimeType}. The ' + 'GestureRecognizerFactory must be specialized with the type of the ' + 'class that it returns from its constructor method.', + ); + configuration.recognizerFactories[type]! + .initializer(newRecognizers[type]!); + } + _disposeRecognizers(); // only disposes the ones that where not re-used above. + _recognizers = newRecognizers; + } + + void _disposeRecognizers() { + if (_recognizers != null) { + for (final GestureRecognizer recognizer in _recognizers!.values) { + recognizer.dispose(); + } + _recognizers = null; + } + } + + // ---- HitTestTarget ---- + + @override + void handleEvent(PointerEvent event, HitTestEntry entry) { + if (event is PointerDownEvent && + configuration.recognizerFactories.isNotEmpty) { + if (_recognizers == null) { + _syncRecognizers(); + } + assert(_recognizers != null); + for (final GestureRecognizer recognizer in _recognizers!.values) { + recognizer.addPointer(event); + } + } + } + + // ---- MouseTrackerAnnotation ---- + + @override + MouseCursor get cursor => configuration.cursor; + + @override + PointerEnterEventListener? get onEnter => configuration.onEnter; + + @override + PointerExitEventListener? get onExit => configuration.onExit; + + @override + bool get validForMouseTracker => true; +} diff --git a/packages/two_dimensional_scrollables/lib/src/table_view/table_cell.dart b/packages/two_dimensional_scrollables/lib/src/table_view/table_cell.dart new file mode 100644 index 00000000000..d071a234967 --- /dev/null +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table_cell.dart @@ -0,0 +1,43 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; + +import 'table.dart'; + +/// The relative position of a child in a [TableViewport] in relation +/// to other children of the viewport, in terms of rows and columns. +/// +/// This subclass translates the abstract [ChildVicinity.xIndex] and +/// [ChildVicinity.yIndex] into terms of rows and columns for ease of use within +/// the context of a [TableView]. +@immutable +class TableVicinity extends ChildVicinity { + /// Creates a reference to a child in a [TableView], with the [xIndex] and + /// [yIndex] converted to terms of [row] and [column]. + const TableVicinity({ + required int row, + required int column, + }) : super(xIndex: column, yIndex: row); + + /// The row index of the child in the [TableView]. + /// + /// Equivalent to the [yIndex]. + int get row => yIndex; + + /// The column index of the child in the [TableView]. + /// + /// Equivalent to the [xIndex]. + int get column => xIndex; + + @override + String toString() => '(row: $row, column: $column)'; +} + +/// Parent data structure used by [RenderTableViewport]. +class TableViewParentData extends TwoDimensionalViewportParentData { + // TODO(Piinks): Add back merged cells here, https://github.com/flutter/flutter/issues/131224 + /// Converts the [ChildVicinity] to a [TableVicinity] for ease of use. + TableVicinity get tableVicinity => vicinity as TableVicinity; +} diff --git a/packages/two_dimensional_scrollables/lib/src/table_view/table_delegate.dart b/packages/two_dimensional_scrollables/lib/src/table_view/table_delegate.dart new file mode 100644 index 00000000000..43cb1c574fd --- /dev/null +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table_delegate.dart @@ -0,0 +1,301 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; + +import 'table.dart'; +import 'table_cell.dart'; +import 'table_span.dart'; + +/// Signature for a function that creates a [TableSpan] for a given index of row +/// or column in a [TableView]. +/// +/// Used by the [TableCellDelegateMixin.buildColumn] and +/// [TableCellDelegateMixin.buildRow] to configure rows and columns in the +/// [TableView]. +typedef TableSpanBuilder = TableSpan Function(int index); + +/// Signature for a function that creates a child [Widget] for a given +/// [TableVicinity] in a [TableView], but may return null. +/// +/// Used by [TableCellBuilderDelegate.builder] to build cells on demand for the +/// table. +typedef TableViewCellBuilder = Widget? Function( + BuildContext context, + TableVicinity vicinity, +); + +/// A mixin that defines the model for a [TwoDimensionalChildDelegate] to be +/// used with a [TableView]. +mixin TableCellDelegateMixin on TwoDimensionalChildDelegate { + /// The number of columns that the table has content for. + /// + /// The [buildColumn] method will be called for indices smaller than the value + /// provided here to learn more about the extent and visual appearance of a + /// particular column. + // TODO(Piinks): land infinite separately, https://github.com/flutter/flutter/issues/131226 + // If null, the table will have an infinite number of columns. + /// + /// The value returned by this getter may be an estimate of the total + /// available columns, but [buildColumn] method must provide a valid + /// [TableSpan] for all indices smaller than this integer. + /// + /// The integer returned by this getter must be larger than (or equal to) the + /// integer returned by [pinnedColumnCount]. + /// + /// If the value returned by this getter changes throughout the lifetime of + /// the delegate object, [notifyListeners] must be called. + int get columnCount; + + /// The number of rows that the table has content for. + /// + /// The [buildRow] method will be called for indices smaller than the value + /// provided here to learn more about the extent and visual appearance of a + /// particular row. + // TODO(Piinks): land infinite separately, https://github.com/flutter/flutter/issues/131226 + // If null, the table will have an infinite number of rows. + /// + /// The value returned by this getter may be an estimate of the total + /// available rows, but [buildRow] method must provide a valid + /// [TableSpan] for all indices smaller than this integer. + /// + /// The integer returned by this getter must be larger than (or equal to) the + /// integer returned by [pinnedRowCount]. + /// + /// If the value returned by this getter changes throughout the lifetime of + /// the delegate object, [notifyListeners] must be called. + int get rowCount; + + /// The number of columns that are permanently shown on the leading vertical + /// edge of the viewport. + /// + /// If scrolling is enabled, other columns will scroll underneath the pinned + /// columns. + /// + /// Just like for regular columns, [buildColumn] method will be consulted for + /// additional information about the pinned column. The indices of pinned + /// columns start at zero and go to `pinnedColumnCount - 1`. + /// + /// The integer returned by this getter must be smaller than (or equal to) the + /// integer returned by [columnCount]. + /// + /// If the value returned by this getter changes throughout the lifetime of + /// the delegate object, [notifyListeners] must be called. + int get pinnedColumnCount => 0; + + /// The number of rows that are permanently shown on the leading horizontal + /// edge of the viewport. + /// + /// If scrolling is enabled, other rows will scroll underneath the pinned + /// rows. + /// + /// Just like for regular rows, [buildRow] will be consulted for + /// additional information about the pinned row. The indices of pinned rows + /// start at zero and go to `pinnedRowCount - 1`. + /// + /// The integer returned by this getter must be smaller than (or equal to) the + /// integer returned by [rowCount]. + /// + /// If the value returned by this getter changes throughout the lifetime of + /// the delegate object, [notifyListeners] must be called. + int get pinnedRowCount => 0; + + /// Builds the [TableSpan] that describes the column at the provided index. + /// + /// The builder must return a valid [TableSpan] for all indices smaller than + /// [columnCount]. + TableSpan buildColumn(int index); + + /// Builds the [TableSpan] that describe the row at the provided index. + /// + /// The builder must return a valid [TableSpan] for all indices smaller than + /// [rowCount]. + TableSpan buildRow(int index); +} + +/// A delegate that supplies children for a [TableViewport] on demand using a +/// builder callback. +class TableCellBuilderDelegate extends TwoDimensionalChildBuilderDelegate + with TableCellDelegateMixin { + /// Creates a lazy building delegate to use with a [TableView]. + TableCellBuilderDelegate({ + required int columnCount, + required int rowCount, + int pinnedColumnCount = 0, + int pinnedRowCount = 0, + super.addRepaintBoundaries = false, + required TableViewCellBuilder cellBuilder, + required TableSpanBuilder columnBuilder, + required TableSpanBuilder rowBuilder, + }) : assert(pinnedColumnCount >= 0), + assert(pinnedRowCount >= 0), + assert(rowCount >= 0), + assert(columnCount >= 0), + assert(pinnedColumnCount <= columnCount), + assert(pinnedRowCount <= rowCount), + _rowBuilder = rowBuilder, + _columnBuilder = columnBuilder, + _pinnedColumnCount = pinnedColumnCount, + _pinnedRowCount = pinnedRowCount, + super( + builder: (BuildContext context, ChildVicinity vicinity) => + cellBuilder(context, vicinity as TableVicinity), + maxXIndex: columnCount - 1, + maxYIndex: rowCount - 1, + ); + + @override + int get columnCount => maxXIndex! + 1; + set columnCount(int value) { + assert(pinnedColumnCount <= value); + // TODO(Piinks): remove once this assertion is added in the super class + assert(value >= -1); + maxXIndex = value - 1; + } + + /// Builds the [TableSpan] that describes the column at the provided index. + /// + /// The builder must return a valid [TableSpan] for all indices smaller than + /// [columnCount]. + final TableSpanBuilder _columnBuilder; + @override + TableSpan buildColumn(int index) => _columnBuilder(index); + + @override + int get pinnedColumnCount => _pinnedColumnCount; + int _pinnedColumnCount; + set pinnedColumnCount(int value) { + assert(value >= 0); + assert(value <= columnCount); + if (pinnedColumnCount == value) { + return; + } + _pinnedColumnCount = value; + notifyListeners(); + } + + @override + int get rowCount => maxYIndex! + 1; + set rowCount(int value) { + assert(pinnedRowCount <= value); + // TODO(Piinks): remove once this assertion is added in the super class + assert(value >= -1); + maxYIndex = value - 1; + } + + /// Builds the [TableSpan] that describes the row at the provided index. + /// + /// The builder must return a valid [TableSpan] for all indices smaller than + /// [rowCount]. + final TableSpanBuilder _rowBuilder; + @override + TableSpan buildRow(int index) => _rowBuilder(index); + + @override + int get pinnedRowCount => _pinnedRowCount; + int _pinnedRowCount; + set pinnedRowCount(int value) { + assert(value >= 0); + assert(value <= rowCount); + if (pinnedRowCount == value) { + return; + } + _pinnedRowCount = value; + notifyListeners(); + } +} + +/// A delegate that supplies children for a [TableViewport] using an +/// explicit two dimensional array. +/// +/// The [children] are accessed for each [TableVicinity.row] and +/// [TableVicinity.column] of the [TwoDimensionalViewport] as +/// `children[vicinity.row][vicinity.column]`. +class TableCellListDelegate extends TwoDimensionalChildListDelegate + with TableCellDelegateMixin { + /// Creates a delegate that supplies children for a [TableView]. + TableCellListDelegate({ + int pinnedColumnCount = 0, + int pinnedRowCount = 0, + super.addRepaintBoundaries, + required List> cells, + required TableSpanBuilder columnBuilder, + required TableSpanBuilder rowBuilder, + }) : assert(pinnedColumnCount >= 0), + assert(pinnedRowCount >= 0), + _columnBuilder = columnBuilder, + _rowBuilder = rowBuilder, + _pinnedColumnCount = pinnedColumnCount, + _pinnedRowCount = pinnedRowCount, + super(children: cells) { + // Even if there are merged cells, they should be represented by the same + // child in each cell location. So all arrays of cells should have the same + // length. + assert( + children.map((List array) => array.length).toSet().length == 1, + 'Each list of Widgets within cells must be of the same length.', + ); + assert(rowCount >= pinnedRowCount); + assert(columnCount >= pinnedColumnCount); + } + + @override + int get columnCount => children.isEmpty ? 0 : children[0].length; + + /// Builds the [TableSpan] that describes the column at the provided index. + /// + /// The builder must return a valid [TableSpan] for all indices smaller than + /// [columnCount]. + final TableSpanBuilder _columnBuilder; + @override + TableSpan buildColumn(int index) => _columnBuilder(index); + + @override + int get pinnedColumnCount => _pinnedColumnCount; + int _pinnedColumnCount; + set pinnedColumnCount(int value) { + assert(value >= 0); + assert(value <= columnCount); + if (pinnedColumnCount == value) { + return; + } + _pinnedColumnCount = value; + notifyListeners(); + } + + @override + int get rowCount => children.length; + + /// Builds the [TableSpan] that describes the row at the provided index. + /// + /// The builder must return a valid [TableSpan] for all indices smaller than + /// [rowCount]. + final TableSpanBuilder _rowBuilder; + @override + TableSpan buildRow(int index) => _rowBuilder(index); + + @override + int get pinnedRowCount => _pinnedRowCount; + int _pinnedRowCount; + set pinnedRowCount(int value) { + assert(value >= 0); + assert(value <= rowCount); + if (pinnedRowCount == value) { + return; + } + _pinnedRowCount = value; + notifyListeners(); + } + + @override + bool shouldRebuild(covariant TableCellListDelegate oldDelegate) { + return columnCount != oldDelegate.columnCount || + _columnBuilder != oldDelegate._columnBuilder || + pinnedColumnCount != oldDelegate.pinnedColumnCount || + rowCount != oldDelegate.rowCount || + _rowBuilder != oldDelegate._rowBuilder || + pinnedRowCount != oldDelegate.pinnedRowCount || + super.shouldRebuild(oldDelegate); + } +} diff --git a/packages/two_dimensional_scrollables/lib/src/table_view/table_span.dart b/packages/two_dimensional_scrollables/lib/src/table_view/table_span.dart new file mode 100644 index 00000000000..fbfd9155a15 --- /dev/null +++ b/packages/two_dimensional_scrollables/lib/src/table_view/table_span.dart @@ -0,0 +1,382 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:math' as math; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +import 'table.dart'; + +/// Defines the extent, visual appearance, and gesture handling of a row or +/// column in a [TableView]. +/// +/// A span refers to either a column or a row in a table. +class TableSpan { + /// Creates a [TableSpan]. + /// + /// The [extent] argument must be provided. + const TableSpan({ + required this.extent, + this.recognizerFactories = const {}, + this.onEnter, + this.onExit, + this.cursor = MouseCursor.defer, + this.backgroundDecoration, + this.foregroundDecoration, + }); + + /// Defines the extent of the span. + /// + /// If the span represents a row, this is the height of the row. If it + /// represents a column, this is the width of the column. + final TableSpanExtent extent; + + /// Factory for creating [GestureRecognizer]s that want to compete for + /// gestures within the [extent] of the span. + /// + /// If this span represents a row, a factory for a [TapGestureRecognizer] + /// could for example be provided here to recognize taps within the bounds + /// of the row. + /// + /// The content of a cell takes precedence in handling pointer events. Next, + /// the recognizers defined for the [TableView.mainAxis], followed by the + /// other [Axis]. + final Map recognizerFactories; + + /// Triggers when a mouse pointer, with or without buttons pressed, has + /// entered the region encompassing the row or column described by this span. + /// + /// This callback is triggered when the pointer has started to be contained by + /// the region, either due to a pointer event, or due to the movement or + /// appearance of the region. This method is always matched by a later + /// [onExit] call. + final PointerEnterEventListener? onEnter; + + /// Triggered when a mouse pointer, with or without buttons pressed, has + /// exited the region encompassing the row or column described by this span. + /// + /// This callback is triggered when the pointer has stopped being contained + /// by the region, either due to a pointer event, or due to the movement or + /// disappearance of the region. This method always matches an earlier + /// [onEnter] call. + final PointerExitEventListener? onExit; + + /// Mouse cursor to show when the mouse hovers over this span. + /// + /// Defaults to [MouseCursor.defer]. + final MouseCursor cursor; + + /// The [TableSpanDecoration] to paint behind the content of this span. + /// + /// The [backgroundDecoration]s of the [TableView.mainAxis] are painted after + /// the [backgroundDecoration]s of the other [Axis]. On top of that, + /// the content of the individual cells in this span are painted, followed by + /// any specified [foregroundDecoration]. + /// + /// The decorations of pinned rows and columns are painted separately from + /// the decorations of unpinned rows and columns, with the unpinned rows and + /// columns being painted first to account for overlap from pinned rows or + /// columns. + final TableSpanDecoration? backgroundDecoration; + + /// The [TableSpanDecoration] to paint in front of the content of this span. + /// + /// After painting any [backgroundDecoration]s, and the content of the + /// individual cells, the [foregroundDecoration] of the [TableView.mainAxis] + /// are painted after the [foregroundDecoration]s of the other [Axis] + /// + /// The decorations of pinned rows and columns are painted separately from + /// the decorations of unpinned rows and columns, with the unpinned rows and + /// columns being painted first to account for overlap from pinned rows or + /// columns. + final TableSpanDecoration? foregroundDecoration; +} + +/// Delegate passed to [TableSpanExtent.calculateExtent] from the +/// [RenderTableViewport] during layout. +/// +/// Provides access to metrics from the [TableView] that a [TableSpanExtent] may +/// need to calculate its extent. +/// +/// Extents will not be computed for every frame unless the delegate has been +/// updated. Otherwise, after the extents are computed during the first layout +/// passed, they are cached and reused in subsequent frames. +class TableSpanExtentDelegate { + /// Creates a [TableSpanExtentDelegate]. + /// + /// Usually, only [TableView]s need to create instances of this class. + const TableSpanExtentDelegate({ + required this.viewportExtent, + required this.precedingExtent, + }); + + /// The size of the viewport in the axis-direction of the span. + /// + /// If the [TableSpanExtent] calculates the extent of a row, this is the + /// height of the viewport. If it calculates the extent of a column, this + /// is the width of the viewport. + final double viewportExtent; + + /// The scroll extent that has already been used up by previous spans. + /// + /// If the [TableSpanExtent] calculates the extent of a row, this is the + /// sum of all row extents prior to this row. If it calculates the extent + /// of a column, this is the sum of all previous columns. + final double precedingExtent; +} + +/// Defines the extent of a [TableSpan]. +/// +/// If the span is a row, its extent is the height of the row. If the span is +/// a column, it's the width of that column. +abstract class TableSpanExtent { + /// Creates a [TableSpanExtent]. + const TableSpanExtent(); + + /// Calculates the actual extent of the span in pixels. + /// + /// To assist with the calculation, table metrics obtained from the provided + /// [TableSpanExtentDelegate] may be used. + double calculateExtent(TableSpanExtentDelegate delegate); +} + +/// A span extent with a fixed [pixels] value. +class FixedTableSpanExtent extends TableSpanExtent { + /// Creates a [FixedTableSpanExtent]. + /// + /// The provided [pixels] value must be equal to or greater then zero. + const FixedTableSpanExtent(this.pixels) : assert(pixels >= 0.0); + + /// The extent of the span in pixels. + final double pixels; + + @override + double calculateExtent(TableSpanExtentDelegate delegate) => pixels; +} + +/// Specified the span extent as a fraction of the viewport extent. +/// +/// For example, a column with a 1.0 as [fraction] will be as wide as the +/// viewport. +class FractionalTableSpanExtent extends TableSpanExtent { + /// Creates a [FractionalTableSpanExtent]. + /// + /// The provided [fraction] value must be equal to or greater than zero. + const FractionalTableSpanExtent( + this.fraction, + ) : assert(fraction >= 0.0); + + /// The fraction of the [TableSpanExtentDelegate.viewportExtent] that the + /// span should occupy. + /// + /// The provided [fraction] value must be equal to or greater than zero. + final double fraction; + + @override + double calculateExtent(TableSpanExtentDelegate delegate) => + delegate.viewportExtent * fraction; +} + +/// Specifies that the span should occupy the remaining space in the viewport. +/// +/// If the previous [TableSpan]s can already fill out the viewport, this will +/// evaluate the span's extent to zero. If the previous spans cannot fill out the +/// viewport, this span's extent will be whatever space is left to fill out the +/// viewport. +/// +/// To avoid that the span's extent evaluates to zero, consider combining this +/// extent with another extent. The following example will make sure that the +/// span's extent is at least 200 pixels, but if there's more than that available +/// in the viewport, it will fill all that space: +/// +/// ```dart +/// const MaxTableSpanExtent(FixedTableSpanExtent(200.0), RemainingTableSpanExtent()); +/// ``` +class RemainingTableSpanExtent extends TableSpanExtent { + /// Creates a [RemainingTableSpanExtent]. + const RemainingTableSpanExtent(); + + @override + double calculateExtent(TableSpanExtentDelegate delegate) { + return math.max(0.0, delegate.viewportExtent - delegate.precedingExtent); + } +} + +/// Signature for a function that combines the result of two +/// [TableSpanExtent.calculateExtent] invocations. +/// +/// Used by [CombiningTableSpanExtent]; +typedef TableSpanExtentCombiner = double Function(double, double); + +/// Runs the result of two [TableSpanExtent]s through a `combiner` function +/// to determine the ultimate pixel extent of a span. +class CombiningTableSpanExtent extends TableSpanExtent { + /// Creates a [CombiningTableSpanExtent]; + const CombiningTableSpanExtent(this._extent1, this._extent2, this._combiner); + + final TableSpanExtent _extent1; + final TableSpanExtent _extent2; + final TableSpanExtentCombiner _combiner; + + @override + double calculateExtent(TableSpanExtentDelegate delegate) { + return _combiner( + _extent1.calculateExtent(delegate), + _extent2.calculateExtent(delegate), + ); + } +} + +/// Returns the larger pixel extent of the two provided [TableSpanExtent]. +class MaxTableSpanExtent extends CombiningTableSpanExtent { + /// Creates a [MaxTableSpanExtent]. + const MaxTableSpanExtent( + TableSpanExtent extent1, + TableSpanExtent extent2, + ) : super(extent1, extent2, math.max); +} + +/// Returns the smaller pixel extent of the two provided [TableSpanExtent]. +class MinTableSpanExtent extends CombiningTableSpanExtent { + /// Creates a [MinTableSpanExtent]. + const MinTableSpanExtent( + TableSpanExtent extent1, + TableSpanExtent extent2, + ) : super(extent1, extent2, math.min); +} + +/// A decoration for a [TableSpan]. +class TableSpanDecoration { + /// Creates a [TableSpanDecoration]. + const TableSpanDecoration({this.border, this.color}); + + /// The border drawn around the span. + final TableSpanBorder? border; + + /// The color to fill the bounds of the span with. + final Color? color; + + /// Called to draw the decoration around a span. + /// + /// The provided [TableSpanDecorationPaintDetails] describes the bounds and + /// orientation of the span that are currently visible inside the viewport of + /// the table. The extent of the actual span may be larger. + /// + /// If a span contains pinned parts, [paint] is invoked separately for the + /// pinned and unpinned parts. For example: If a row contains a pinned column, + /// paint is called with the [TableSpanDecorationPaintDetails.rect] for the + /// cell representing the pinned column and separately with another + /// [TableSpanDecorationPaintDetails.rect] containing all the other unpinned + /// cells. + void paint(TableSpanDecorationPaintDetails details) { + if (color != null) { + details.canvas.drawRect( + details.rect, + Paint() + ..color = color! + ..isAntiAlias = false, + ); + } + if (border != null) { + border!.paint(details); + } + } +} + +/// Describes the border for a [TableSpan]. +class TableSpanBorder { + /// Creates a [TableSpanBorder]. + const TableSpanBorder({ + this.trailing = BorderSide.none, + this.leading = BorderSide.none, + }); + + /// The border to draw on the trailing side of the span, based on the + /// [AxisDirection]. + /// + /// The trailing side of a row is the bottom when [Axis.vertical] is + /// [AxisDirection.down], the trailing side of a column + /// is its right side when the [Axis.horizontal] is [AxisDirection.right]. + final BorderSide trailing; + + /// The border to draw on the leading side of the span. + /// + /// The leading side of a row is the top when [Axis.vertical] is + /// [AxisDirection.down], the leading side of a column + /// is its left side when the [Axis.horizontal] is [AxisDirection.right]. + final BorderSide leading; + + /// Called to draw the border around a span. + /// + /// If the span represents a row, `axisDirection` will be [AxisDirection.left] + /// or [AxisDirection.right]. For columns, the `axisDirection` will be + /// [AxisDirection.down] or [AxisDirection.up]. + /// + /// The provided [TableSpanDecorationPaintDetails] describes the bounds and + /// orientation of the span that are currently visible inside the viewport of + /// the table. The extent of the actual span may be larger. + /// + /// If a span contains pinned parts, [paint] is invoked separately for the + /// pinned and unpinned parts. For example: If a row contains a pinned column, + /// paint is called with the [TableSpanDecorationPaintDetails.rect] for the + /// cell representing the pinned column and separately with another + /// [TableSpanDecorationPaintDetails.rect] containing all the other unpinned + /// cells. + void paint(TableSpanDecorationPaintDetails details) { + final AxisDirection axisDirection = details.axisDirection; + switch (axisDirectionToAxis(axisDirection)) { + case Axis.horizontal: + paintBorder( + details.canvas, + details.rect, + top: axisDirection == AxisDirection.right ? leading : trailing, + bottom: axisDirection == AxisDirection.right ? trailing : leading, + ); + break; + case Axis.vertical: + paintBorder( + details.canvas, + details.rect, + left: axisDirection == AxisDirection.down ? leading : trailing, + right: axisDirection == AxisDirection.down ? trailing : leading, + ); + break; + } + } +} + +/// Provides the details of a given [TableSpanDecoration] for painting. +/// +/// Created during paint by the [RenderTableViewport] for the +/// [TableSpan.foregroundDecoration] and [TableSpan.backgroundDecoration]. +class TableSpanDecorationPaintDetails { + /// Creates the details needed to paint a [TableSpanDecoration]. + /// + /// The [canvas], [rect], and [axisDirection] must be provided. + TableSpanDecorationPaintDetails({ + required this.canvas, + required this.rect, + required this.axisDirection, + }); + + /// The [Canvas] that the [TableSpanDecoration] will be painted to. + final Canvas canvas; + + /// A [Rect] representing the visible area of a row or column in the + /// [TableView], as represented by a [TableSpan]. + /// + /// This Rect contains all of the visible children in a given row or column, + /// which is the area the [TableSpanDecoration] will be applied to. + final Rect rect; + + /// The [AxisDirection] of the [Axis] of the [TableSpan]. + /// + /// When [AxisDirection.down] or [AxisDirection.up], which would be + /// [Axis.vertical], a column is being painted. When [AxisDirection.left] or + /// [AxisDirection.right], which would be [Axis.horizontal], a row is being + /// painted. + final AxisDirection axisDirection; +} diff --git a/packages/two_dimensional_scrollables/lib/two_dimensional_scrollables.dart b/packages/two_dimensional_scrollables/lib/two_dimensional_scrollables.dart new file mode 100644 index 00000000000..3107e6e1c0b --- /dev/null +++ b/packages/two_dimensional_scrollables/lib/two_dimensional_scrollables.dart @@ -0,0 +1,13 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// The [TableView] and associated widgets. +/// +/// To use, import `package:two_dimensional_scrollables/two_dimensional_scrollables.dart`. +library table_view; + +export 'src/table_view/table.dart'; +export 'src/table_view/table_cell.dart'; +export 'src/table_view/table_delegate.dart'; +export 'src/table_view/table_span.dart'; diff --git a/packages/two_dimensional_scrollables/pubspec.yaml b/packages/two_dimensional_scrollables/pubspec.yaml new file mode 100644 index 00000000000..5b861ffd7cc --- /dev/null +++ b/packages/two_dimensional_scrollables/pubspec.yaml @@ -0,0 +1,18 @@ +name: two_dimensional_scrollables +description: Widgets that scroll using the two dimensional scrolling foundation. +version: 0.0.1 +repository: https://github.com/flutter/packages/tree/main/packages/two_dimensional_scrollables +issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+two_dimensional_scrollables%22+ + +environment: + sdk: '>=3.1.0 <4.0.0' + flutter: ">=3.13.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_lints: ^2.0.0 + flutter_test: + sdk: flutter diff --git a/packages/two_dimensional_scrollables/test/table_view/goldens/tableSpanDecoration.defaultMainAxis.png b/packages/two_dimensional_scrollables/test/table_view/goldens/tableSpanDecoration.defaultMainAxis.png new file mode 100644 index 00000000000..8fe4353ef52 Binary files /dev/null and b/packages/two_dimensional_scrollables/test/table_view/goldens/tableSpanDecoration.defaultMainAxis.png differ diff --git a/packages/two_dimensional_scrollables/test/table_view/goldens/tableSpanDecoration.horizontalMainAxis.png b/packages/two_dimensional_scrollables/test/table_view/goldens/tableSpanDecoration.horizontalMainAxis.png new file mode 100644 index 00000000000..f9a1bc81fb2 Binary files /dev/null and b/packages/two_dimensional_scrollables/test/table_view/goldens/tableSpanDecoration.horizontalMainAxis.png differ diff --git a/packages/two_dimensional_scrollables/test/table_view/table_cell_test.dart b/packages/two_dimensional_scrollables/test/table_view/table_cell_test.dart new file mode 100644 index 00000000000..2eace84b18a --- /dev/null +++ b/packages/two_dimensional_scrollables/test/table_view/table_cell_test.dart @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; + +void main() { + test('TableVicinity converts ChildVicinity', () { + const TableVicinity vicinity = TableVicinity(column: 5, row: 10); + expect(vicinity.xIndex, 5); + expect(vicinity.yIndex, 10); + expect(vicinity.toString(), '(row: 10, column: 5)'); + }); + + // TODO(Piinks): TableViewCell tests for merged cells, follow up change. +} diff --git a/packages/two_dimensional_scrollables/test/table_view/table_delegate_test.dart b/packages/two_dimensional_scrollables/test/table_view/table_delegate_test.dart new file mode 100644 index 00000000000..b9e2da26b80 --- /dev/null +++ b/packages/two_dimensional_scrollables/test/table_view/table_delegate_test.dart @@ -0,0 +1,499 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; + +const TableSpan span = TableSpan(extent: FixedTableSpanExtent(50)); +const Widget cell = SizedBox.shrink(); + +void main() { + group('TableCellBuilderDelegate', () { + test('asserts valid counts for rows and columns', () { + TableCellBuilderDelegate? delegate; + expect( + () { + delegate = TableCellBuilderDelegate( + cellBuilder: (_, __) => cell, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + columnCount: 1, + rowCount: 1, + pinnedColumnCount: -1, // asserts + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('pinnedColumnCount >= 0'), + ), + ), + ); + + expect( + () { + delegate = TableCellBuilderDelegate( + cellBuilder: (_, __) => cell, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + columnCount: 1, + rowCount: 1, + pinnedRowCount: -1, // asserts + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('pinnedRowCount >= 0'), + ), + ), + ); + + expect( + () { + delegate = TableCellBuilderDelegate( + cellBuilder: (_, __) => cell, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + columnCount: 1, + rowCount: -1, // asserts + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('rowCount >= 0'), + ), + ), + ); + + expect( + () { + delegate = TableCellBuilderDelegate( + cellBuilder: (_, __) => cell, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + columnCount: -1, // asserts + rowCount: 1, + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('columnCount >= 0'), + ), + ), + ); + + expect( + () { + delegate = TableCellBuilderDelegate( + cellBuilder: (_, __) => cell, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + columnCount: 1, + pinnedColumnCount: 2, // asserts + rowCount: 1, + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('pinnedColumnCount <= columnCount'), + ), + ), + ); + + expect( + () { + delegate = TableCellBuilderDelegate( + cellBuilder: (_, __) => cell, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + columnCount: 1, + pinnedRowCount: 2, // asserts + rowCount: 1, + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('pinnedRowCount <= rowCount'), + ), + ), + ); + + expect(delegate, isNull); + }); + + test('sets max x and y index of super class', () { + final TableCellBuilderDelegate delegate = TableCellBuilderDelegate( + cellBuilder: (_, __) => cell, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + columnCount: 5, + rowCount: 6, + ); + expect(delegate.maxYIndex, 5); // rows + expect(delegate.maxXIndex, 4); // columns + }); + + test('Notifies listeners & rebuilds', () { + int notified = 0; + TableCellBuilderDelegate oldDelegate; + TableSpan spanBuilder(int index) => span; + Widget cellBuilder(BuildContext context, TableVicinity vicinity) => cell; + final TableCellBuilderDelegate delegate = TableCellBuilderDelegate( + cellBuilder: cellBuilder, + columnBuilder: spanBuilder, + rowBuilder: spanBuilder, + columnCount: 5, + pinnedColumnCount: 1, + rowCount: 6, + pinnedRowCount: 2, + ); + delegate.addListener(() { + notified++; + }); + + // change column count + oldDelegate = delegate; + delegate.columnCount = 6; + expect(notified, 1); + expect(delegate.shouldRebuild(oldDelegate), isTrue); + + // change pinned column count + oldDelegate = delegate; + delegate.pinnedColumnCount = 2; + expect(notified, 2); + expect(delegate.shouldRebuild(oldDelegate), isTrue); + + // change row count + oldDelegate = delegate; + delegate.rowCount = 7; + expect(notified, 3); + expect(delegate.shouldRebuild(oldDelegate), isTrue); + + // change pinned row count + oldDelegate = delegate; + delegate.pinnedRowCount = 3; + expect(notified, 4); + expect(delegate.shouldRebuild(oldDelegate), isTrue); + + // Builder delegate always returns true. + expect(delegate.shouldRebuild(delegate), isTrue); + }); + }); + + group('TableCellListDelegate', () { + test('asserts valid counts for rows and columns', () { + TableCellListDelegate? delegate; + expect( + () { + delegate = TableCellListDelegate( + cells: >[[]], + columnBuilder: (_) => span, + rowBuilder: (_) => span, + pinnedColumnCount: -1, // asserts + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('pinnedColumnCount >= 0'), + ), + ), + ); + expect( + () { + delegate = TableCellListDelegate( + cells: >[[]], + columnBuilder: (_) => span, + rowBuilder: (_) => span, + pinnedRowCount: -1, // asserts + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('pinnedRowCount >= 0'), + ), + ), + ); + expect( + () { + delegate = TableCellListDelegate( + cells: >[ + [cell, cell], + [cell, cell], + ], + columnBuilder: (_) => span, + rowBuilder: (_) => span, + pinnedRowCount: 3, // asserts + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('rowCount >= pinnedRowCount'), + ), + ), + ); + expect( + () { + delegate = TableCellListDelegate( + cells: >[ + [cell, cell], + [cell, cell], + ], + columnBuilder: (_) => span, + rowBuilder: (_) => span, + pinnedColumnCount: 3, // asserts + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('columnCount >= pinnedColumnCount'), + ), + ), + ); + expect(delegate, isNull); + }); + + test('Asserts child lists lengths match', () { + TableCellListDelegate? delegate; + expect( + () { + delegate = TableCellListDelegate( + cells: >[ + [cell, cell], + [cell, cell, cell], + ], + columnBuilder: (_) => span, + rowBuilder: (_) => span, + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains( + 'Each list of Widgets within cells must be of the same length.'), + ), + ), + ); + expect(delegate, isNull); + }); + + test('Notifies listeners & rebuilds', () { + int notified = 0; + TableCellListDelegate oldDelegate; + TableSpan spanBuilder(int index) => span; + TableCellListDelegate delegate = TableCellListDelegate( + cells: >[ + [cell, cell], + [cell, cell], + ], + columnBuilder: spanBuilder, + rowBuilder: spanBuilder, + pinnedColumnCount: 1, + pinnedRowCount: 1, + ); + delegate.addListener(() { + notified++; + }); + + // change pinned column count + oldDelegate = delegate; + delegate.pinnedColumnCount = 0; + expect(notified, 1); + + // change pinned row count + oldDelegate = delegate; + delegate.pinnedRowCount = 0; + expect(notified, 2); + + // shouldRebuild + // columnCount + oldDelegate = delegate; + delegate = TableCellListDelegate( + cells: >[ + [cell, cell, cell], + [cell, cell, cell], + ], + columnBuilder: spanBuilder, + rowBuilder: spanBuilder, + ); + expect(delegate.shouldRebuild(oldDelegate), isTrue); + + // columnBuilder + oldDelegate = delegate; + delegate = TableCellListDelegate( + cells: >[ + [cell, cell, cell], + [cell, cell, cell], + ], + columnBuilder: (int index) => const TableSpan( + extent: FixedTableSpanExtent(150), + ), + rowBuilder: spanBuilder, + ); + expect(delegate.shouldRebuild(oldDelegate), isTrue); + + // rowCount + oldDelegate = delegate; + delegate = TableCellListDelegate( + cells: >[ + [cell, cell, cell], + [cell, cell, cell], + [cell, cell, cell], + ], + columnBuilder: (int index) => const TableSpan( + extent: FixedTableSpanExtent(150), + ), + rowBuilder: spanBuilder, + ); + expect(delegate.shouldRebuild(oldDelegate), isTrue); + + // rowBuilder + oldDelegate = delegate; + delegate = TableCellListDelegate( + cells: >[ + [cell, cell, cell], + [cell, cell, cell], + [cell, cell, cell], + ], + columnBuilder: (int index) => const TableSpan( + extent: FixedTableSpanExtent(150), + ), + rowBuilder: (int index) => const TableSpan( + extent: RemainingTableSpanExtent(), + ), + ); + expect(delegate.shouldRebuild(oldDelegate), isTrue); + + // pinned row count + oldDelegate = delegate; + delegate = TableCellListDelegate( + cells: >[ + [cell, cell, cell], + [cell, cell, cell], + [cell, cell, cell], + ], + columnBuilder: (int index) => const TableSpan( + extent: FixedTableSpanExtent(150), + ), + rowBuilder: (int index) => const TableSpan( + extent: RemainingTableSpanExtent(), + ), + pinnedRowCount: 2, + ); + expect(delegate.shouldRebuild(oldDelegate), isTrue); + + // pinned column count + oldDelegate = delegate; + delegate = TableCellListDelegate( + cells: >[ + [cell, cell, cell], + [cell, cell, cell], + [cell, cell, cell], + ], + columnBuilder: (int index) => const TableSpan( + extent: FixedTableSpanExtent(150), + ), + rowBuilder: (int index) => const TableSpan( + extent: RemainingTableSpanExtent(), + ), + pinnedColumnCount: 2, + pinnedRowCount: 2, + ); + expect(delegate.shouldRebuild(oldDelegate), isTrue); + + // Nothing changed + expect(delegate.shouldRebuild(delegate), isFalse); + }); + + test('Changing pinned row and column counts asserts valid values', () { + final TableCellListDelegate delegate = TableCellListDelegate( + cells: >[ + [cell, cell, cell], + [cell, cell, cell], + [cell, cell, cell], + ], + columnBuilder: (int index) => const TableSpan( + extent: FixedTableSpanExtent(150), + ), + rowBuilder: (int index) => const TableSpan( + extent: RemainingTableSpanExtent(), + ), + pinnedColumnCount: 2, + pinnedRowCount: 2, + ); + + expect( + () { + delegate.pinnedColumnCount = -1; + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('value >= 0'), + ), + ), + ); + + expect( + () { + delegate.pinnedRowCount = -1; + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('value >= 0'), + ), + ), + ); + + expect( + () { + delegate.pinnedColumnCount = 4; + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('value <= columnCount'), + ), + ), + ); + + expect( + () { + delegate.pinnedRowCount = 4; + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('value <= rowCount'), + ), + ), + ); + }); + }); +} diff --git a/packages/two_dimensional_scrollables/test/table_view/table_span_test.dart b/packages/two_dimensional_scrollables/test/table_view/table_span_test.dart new file mode 100644 index 00000000000..04618bad2b6 --- /dev/null +++ b/packages/two_dimensional_scrollables/test/table_view/table_span_test.dart @@ -0,0 +1,201 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; + +void main() { + group('TableSpanExtent', () { + test('FixedTableSpanExtent', () { + FixedTableSpanExtent extent = const FixedTableSpanExtent(150); + expect( + extent.calculateExtent( + const TableSpanExtentDelegate(precedingExtent: 0, viewportExtent: 0), + ), + 150, + ); + expect( + extent.calculateExtent( + const TableSpanExtentDelegate( + precedingExtent: 100, viewportExtent: 1000), + ), + 150, + ); + // asserts value is valid + expect( + () { + extent = FixedTableSpanExtent(-100); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('pixels >= 0.0'), + ), + ), + ); + }); + + test('FractionalTableSpanExtent', () { + FractionalTableSpanExtent extent = const FractionalTableSpanExtent(0.5); + expect( + extent.calculateExtent( + const TableSpanExtentDelegate(precedingExtent: 0, viewportExtent: 0), + ), + 0.0, + ); + expect( + extent.calculateExtent( + const TableSpanExtentDelegate( + precedingExtent: 100, viewportExtent: 1000), + ), + 500, + ); + // asserts value is valid + expect( + () { + extent = FractionalTableSpanExtent(-20); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('fraction >= 0.0'), + ), + ), + ); + }); + + test('RemainingTableSpanExtent', () { + const RemainingTableSpanExtent extent = RemainingTableSpanExtent(); + expect( + extent.calculateExtent( + const TableSpanExtentDelegate(precedingExtent: 0, viewportExtent: 0), + ), + 0.0, + ); + expect( + extent.calculateExtent( + const TableSpanExtentDelegate( + precedingExtent: 100, viewportExtent: 1000), + ), + 900, + ); + }); + + test('CombiningTableSpanExtent', () { + final CombiningTableSpanExtent extent = CombiningTableSpanExtent( + const FixedTableSpanExtent(100), + const RemainingTableSpanExtent(), + (double a, double b) { + return a + b; + }, + ); + expect( + extent.calculateExtent( + const TableSpanExtentDelegate(precedingExtent: 0, viewportExtent: 0), + ), + 100, + ); + expect( + extent.calculateExtent( + const TableSpanExtentDelegate( + precedingExtent: 100, viewportExtent: 1000), + ), + 1000, + ); + }); + + test('MaxTableSpanExtent', () { + const MaxTableSpanExtent extent = MaxTableSpanExtent( + FixedTableSpanExtent(100), + RemainingTableSpanExtent(), + ); + expect( + extent.calculateExtent( + const TableSpanExtentDelegate(precedingExtent: 0, viewportExtent: 0), + ), + 100, + ); + expect( + extent.calculateExtent( + const TableSpanExtentDelegate( + precedingExtent: 100, viewportExtent: 1000), + ), + 900, + ); + }); + + test('MinTableSpanExtent', () { + const MinTableSpanExtent extent = MinTableSpanExtent( + FixedTableSpanExtent(100), + RemainingTableSpanExtent(), + ); + expect( + extent.calculateExtent( + const TableSpanExtentDelegate(precedingExtent: 0, viewportExtent: 0), + ), + 0, + ); + expect( + extent.calculateExtent( + const TableSpanExtentDelegate( + precedingExtent: 100, viewportExtent: 1000), + ), + 100, + ); + }); + }); + + test('TableSpanDecoration', () { + TableSpanDecoration decoration = const TableSpanDecoration( + color: Color(0xffff0000), + ); + final TestCanvas canvas = TestCanvas(); + const Rect rect = Rect.fromLTWH(0, 0, 10, 10); + final TableSpanDecorationPaintDetails details = + TableSpanDecorationPaintDetails( + canvas: canvas, + rect: rect, + axisDirection: AxisDirection.down, + ); + decoration.paint(details); + expect(canvas.rect, rect); + expect(canvas.paint.color, const Color(0xffff0000)); + expect(canvas.paint.isAntiAlias, isFalse); + final TestTableSpanBorder border = TestTableSpanBorder( + leading: const BorderSide(), + ); + decoration = TableSpanDecoration(border: border); + decoration.paint(details); + expect(border.details, details); + }); +} + +class TestCanvas implements Canvas { + final List noSuchMethodInvocations = []; + late Rect rect; + late Paint paint; + + @override + void drawRect(Rect rect, Paint paint) { + this.rect = rect; + this.paint = paint; + } + + @override + void noSuchMethod(Invocation invocation) { + noSuchMethodInvocations.add(invocation); + } +} + +class TestTableSpanBorder extends TableSpanBorder { + TestTableSpanBorder({super.leading}); + TableSpanDecorationPaintDetails? details; + @override + void paint(TableSpanDecorationPaintDetails details) { + this.details = details; + } +} diff --git a/packages/two_dimensional_scrollables/test/table_view/table_test.dart b/packages/two_dimensional_scrollables/test/table_view/table_test.dart new file mode 100644 index 00000000000..b3e27bfa42b --- /dev/null +++ b/packages/two_dimensional_scrollables/test/table_view/table_test.dart @@ -0,0 +1,1135 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io' show Platform; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:two_dimensional_scrollables/two_dimensional_scrollables.dart'; + +const TableSpan span = TableSpan(extent: FixedTableSpanExtent(100)); +const Widget cell = SizedBox.shrink(); + +TableSpan getTappableSpan(int index, VoidCallback callback) { + return TableSpan( + extent: const FixedTableSpanExtent(100), + recognizerFactories: { + TapGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + () => TapGestureRecognizer(), + (TapGestureRecognizer t) => t.onTap = () => callback(), + ), + }, + ); +} + +TableSpan getMouseTrackingSpan( + int index, { + PointerEnterEventListener? onEnter, + PointerExitEventListener? onExit, +}) { + return TableSpan( + extent: const FixedTableSpanExtent(100), + onEnter: onEnter, + onExit: onExit, + cursor: SystemMouseCursors.cell, + ); +} + +final bool masterChannel = !Platform.environment.containsKey('CHANNEL') || + Platform.environment['CHANNEL'] == 'master'; + +// TODO(Piinks): Remove once painting can be validated by mock_canvas in +// flutter_test, and re-enable web tests in https://github.com/flutter/flutter/issues/132782 +// Regenerate goldens on a Mac computer by running `flutter test --update-goldens` +final bool runGoldens = Platform.isMacOS && masterChannel; + +void main() { + group('TableView.builder', () { + test('creates correct delegate', () { + final TableView tableView = TableView.builder( + columnCount: 3, + rowCount: 2, + rowBuilder: (_) => span, + columnBuilder: (_) => span, + cellBuilder: (_, __) => cell, + ); + final TableCellBuilderDelegate delegate = + tableView.delegate as TableCellBuilderDelegate; + expect(delegate.pinnedRowCount, 0); + expect(delegate.pinnedRowCount, 0); + expect(delegate.rowCount, 2); + expect(delegate.columnCount, 3); + expect(delegate.buildColumn(0), span); + expect(delegate.buildRow(0), span); + expect( + delegate.builder( + _NullBuildContext(), + const TableVicinity(row: 0, column: 0), + ), + cell, + ); + }); + + test('asserts correct counts', () { + TableView? tableView; + expect( + () { + tableView = TableView.builder( + cellBuilder: (_, __) => cell, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + columnCount: 1, + rowCount: 1, + pinnedColumnCount: -1, // asserts + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('pinnedColumnCount >= 0'), + ), + ), + ); + expect( + () { + tableView = TableView.builder( + cellBuilder: (_, __) => cell, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + columnCount: 1, + rowCount: 1, + pinnedRowCount: -1, // asserts + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('pinnedRowCount >= 0'), + ), + ), + ); + expect( + () { + tableView = TableView.builder( + cellBuilder: (_, __) => cell, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + columnCount: 1, + rowCount: -1, // asserts + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('rowCount >= 0'), + ), + ), + ); + expect( + () { + tableView = TableView.builder( + cellBuilder: (_, __) => cell, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + columnCount: -1, // asserts + rowCount: 1, + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('columnCount >= 0'), + ), + ), + ); + expect( + () { + tableView = TableView.builder( + cellBuilder: (_, __) => cell, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + columnCount: 1, + pinnedColumnCount: 2, // asserts + rowCount: 1, + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('columnCount >= pinnedColumnCount'), + ), + ), + ); + expect( + () { + tableView = TableView.builder( + cellBuilder: (_, __) => cell, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + columnCount: 1, + pinnedRowCount: 2, // asserts + rowCount: 1, + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('rowCount >= pinnedRowCount'), + ), + ), + ); + expect(tableView, isNull); + }); + }); + + group('TableView.list', () { + test('creates correct delegate', () { + final TableView tableView = TableView.list( + rowBuilder: (_) => span, + columnBuilder: (_) => span, + cells: const >[ + [cell, cell, cell], + [cell, cell, cell] + ], + ); + final TableCellListDelegate delegate = + tableView.delegate as TableCellListDelegate; + expect(delegate.pinnedRowCount, 0); + expect(delegate.pinnedRowCount, 0); + expect(delegate.rowCount, 2); + expect(delegate.columnCount, 3); + expect(delegate.buildColumn(0), span); + expect(delegate.buildRow(0), span); + expect(delegate.children[0][0], cell); + }); + + test('asserts correct counts', () { + TableView? tableView; + expect( + () { + tableView = TableView.list( + cells: const >[ + [cell] + ], + columnBuilder: (_) => span, + rowBuilder: (_) => span, + pinnedColumnCount: -1, // asserts + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('pinnedColumnCount >= 0'), + ), + ), + ); + expect( + () { + tableView = TableView.list( + cells: const >[ + [cell] + ], + columnBuilder: (_) => span, + rowBuilder: (_) => span, + pinnedRowCount: -1, // asserts + ); + }, + throwsA( + isA().having( + (AssertionError error) => error.toString(), + 'description', + contains('pinnedRowCount >= 0'), + ), + ), + ); + expect(tableView, isNull); + }); + }); + + group('RenderTableViewport', () { + testWidgets('parent data and table vicinities', + (WidgetTester tester) async { + final Map childKeys = + {}; + const TableSpan span = TableSpan(extent: FixedTableSpanExtent(200)); + final TableView tableView = TableView.builder( + rowCount: 5, + columnCount: 5, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + childKeys[vicinity] = childKeys[vicinity] ?? UniqueKey(); + return SizedBox.square(key: childKeys[vicinity], dimension: 200); + }, + ); + TableViewParentData parentDataOf(RenderBox child) { + return child.parentData! as TableViewParentData; + } + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + final RenderTwoDimensionalViewport viewport = getViewport( + tester, + childKeys.values.first, + ); + expect(viewport.mainAxis, Axis.vertical); + // first child + TableVicinity vicinity = const TableVicinity(column: 0, row: 0); + expect( + parentDataOf(viewport.firstChild!).vicinity, + vicinity, + ); + TableViewParentData parentData = parentDataOf( + tester.renderObject(find.byKey(childKeys[vicinity]!)), + ); + expect(parentData.vicinity, vicinity); + expect(parentData.layoutOffset, Offset.zero); + expect(parentData.isVisible, isTrue); + // after first child + vicinity = const TableVicinity(column: 1, row: 0); + expect( + parentDataOf(viewport.childAfter(viewport.firstChild!)!).vicinity, + vicinity, + ); + parentData = parentDataOf( + tester.renderObject(find.byKey(childKeys[vicinity]!)), + ); + expect(parentData.vicinity, vicinity); + expect(parentData.layoutOffset, const Offset(200, 0.0)); + expect(parentData.isVisible, isTrue); + // before first child (none) + expect( + viewport.childBefore(viewport.firstChild!), + isNull, + ); + + // last child + vicinity = const TableVicinity(column: 4, row: 4); + expect( + parentDataOf(viewport.lastChild!).vicinity, + vicinity, + ); + parentData = parentDataOf( + tester.renderObject(find.byKey(childKeys[vicinity]!)), + ); + expect(parentData.vicinity, vicinity); + expect(parentData.layoutOffset, const Offset(800.0, 800.0)); + expect(parentData.isVisible, isFalse); + // after last child (none) + expect( + viewport.childAfter(viewport.lastChild!), + isNull, + ); + // before last child + vicinity = const TableVicinity(column: 3, row: 4); + expect( + parentDataOf(viewport.childBefore(viewport.lastChild!)!).vicinity, + vicinity, + ); + parentData = parentDataOf( + tester.renderObject(find.byKey(childKeys[vicinity]!)), + ); + expect(parentData.vicinity, vicinity); + expect(parentData.layoutOffset, const Offset(600.0, 800.0)); + expect(parentData.isVisible, isFalse); + }); + + testWidgets('TableSpan gesture hit testing', (WidgetTester tester) async { + int tapCounter = 0; + // Rows + TableView tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + columnBuilder: (_) => span, + rowBuilder: (int index) => index.isEven + ? getTappableSpan( + index, + () => tapCounter++, + ) + : span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 100, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + // Even rows are set up for taps. + expect(tapCounter, 0); + // Tap along along a row + await tester.tap(find.text('Row: 0 Column: 0')); + await tester.tap(find.text('Row: 0 Column: 1')); + await tester.tap(find.text('Row: 0 Column: 2')); + await tester.tap(find.text('Row: 0 Column: 3')); + expect(tapCounter, 4); + // Tap along some odd rows + await tester.tap(find.text('Row: 1 Column: 0')); + await tester.tap(find.text('Row: 1 Column: 1')); + await tester.tap(find.text('Row: 3 Column: 2')); + await tester.tap(find.text('Row: 5 Column: 3')); + expect(tapCounter, 4); + // Check other even rows + await tester.tap(find.text('Row: 2 Column: 1')); + await tester.tap(find.text('Row: 2 Column: 2')); + await tester.tap(find.text('Row: 4 Column: 4')); + await tester.tap(find.text('Row: 4 Column: 5')); + expect(tapCounter, 8); + + // Columns + tapCounter = 0; + tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + rowBuilder: (_) => span, + columnBuilder: (int index) => index.isEven + ? getTappableSpan( + index, + () => tapCounter++, + ) + : span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 100, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + // Even columns are set up for taps. + expect(tapCounter, 0); + // Tap along along a column + await tester.tap(find.text('Row: 1 Column: 0')); + await tester.tap(find.text('Row: 2 Column: 0')); + await tester.tap(find.text('Row: 3 Column: 0')); + await tester.tap(find.text('Row: 4 Column: 0')); + expect(tapCounter, 4); + // Tap along some odd columns + await tester.tap(find.text('Row: 1 Column: 1')); + await tester.tap(find.text('Row: 2 Column: 1')); + await tester.tap(find.text('Row: 3 Column: 3')); + await tester.tap(find.text('Row: 4 Column: 3')); + expect(tapCounter, 4); + // Check other even columns + await tester.tap(find.text('Row: 2 Column: 2')); + await tester.tap(find.text('Row: 3 Column: 2')); + await tester.tap(find.text('Row: 4 Column: 4')); + await tester.tap(find.text('Row: 5 Column: 4')); + expect(tapCounter, 8); + + // Intersecting - main axis sets precedence + int rowTapCounter = 0; + int columnTapCounter = 0; + tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + rowBuilder: (int index) => index.isEven + ? getTappableSpan( + index, + () => rowTapCounter++, + ) + : span, + columnBuilder: (int index) => index.isEven + ? getTappableSpan( + index, + () => columnTapCounter++, + ) + : span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 100, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + // Even columns and rows are set up for taps, mainAxis is vertical by + // default, mening row major order. Rows should take precedence where they + // intersect at even indices. + expect(columnTapCounter, 0); + expect(rowTapCounter, 0); + // Tap where non intersecting, but even. + await tester.tap(find.text('Row: 2 Column: 3')); // Row + await tester.tap(find.text('Row: 4 Column: 5')); // Row + await tester.tap(find.text('Row: 3 Column: 2')); // Column + await tester.tap(find.text('Row: 1 Column: 6')); // Column + expect(columnTapCounter, 2); + expect(rowTapCounter, 2); + // Tap where both are odd and nothing should receive a tap. + await tester.tap(find.text('Row: 1 Column: 1')); + await tester.tap(find.text('Row: 3 Column: 1')); + await tester.tap(find.text('Row: 3 Column: 3')); + await tester.tap(find.text('Row: 5 Column: 3')); + expect(columnTapCounter, 2); + expect(rowTapCounter, 2); + // Check intersections. + await tester.tap(find.text('Row: 2 Column: 2')); + await tester.tap(find.text('Row: 4 Column: 2')); + await tester.tap(find.text('Row: 2 Column: 4')); + await tester.tap(find.text('Row: 4 Column: 4')); + expect(columnTapCounter, 2); + expect(rowTapCounter, 6); // Rows took precedence + + // Change mainAxis + rowTapCounter = 0; + columnTapCounter = 0; + tableView = TableView.builder( + mainAxis: Axis.horizontal, + rowCount: 50, + columnCount: 50, + rowBuilder: (int index) => index.isEven + ? getTappableSpan( + index, + () => rowTapCounter++, + ) + : span, + columnBuilder: (int index) => index.isEven + ? getTappableSpan( + index, + () => columnTapCounter++, + ) + : span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 100, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + expect(rowTapCounter, 0); + expect(columnTapCounter, 0); + + // Check intersections. + await tester.tap(find.text('Row: 2 Column: 2')); + await tester.tap(find.text('Row: 4 Column: 2')); + await tester.tap(find.text('Row: 2 Column: 4')); + await tester.tap(find.text('Row: 4 Column: 4')); + expect(columnTapCounter, 4); // Columns took precedence + expect(rowTapCounter, 0); + }); + + testWidgets('provides correct details in TableSpanExtentDelegate', + (WidgetTester tester) async { + final TestTableSpanExtent columnExtent = TestTableSpanExtent(); + final TestTableSpanExtent rowExtent = TestTableSpanExtent(); + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + final TableView tableView = TableView.builder( + rowCount: 10, + columnCount: 10, + columnBuilder: (_) => TableSpan(extent: columnExtent), + rowBuilder: (_) => TableSpan(extent: rowExtent), + cellBuilder: (_, TableVicinity vicinity) { + return const SizedBox.square(dimension: 100); + }, + verticalDetails: ScrollableDetails.vertical( + controller: verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + controller: horizontalController, + ), + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + // Represents the last delegate provided to the last row and column + expect(columnExtent.delegate.precedingExtent, 900.0); + expect(columnExtent.delegate.viewportExtent, 800.0); + expect(rowExtent.delegate.precedingExtent, 900.0); + expect(rowExtent.delegate.viewportExtent, 600.0); + + verticalController.jumpTo(10.0); + await tester.pump(); + expect(verticalController.position.pixels, 10.0); + expect(horizontalController.position.pixels, 0.0); + // Represents the last delegate provided to the last row and column + expect(columnExtent.delegate.precedingExtent, 900.0); + expect(columnExtent.delegate.viewportExtent, 800.0); + expect(rowExtent.delegate.precedingExtent, 900.0); + expect(rowExtent.delegate.viewportExtent, 600.0); + + horizontalController.jumpTo(10.0); + await tester.pump(); + expect(verticalController.position.pixels, 10.0); + expect(horizontalController.position.pixels, 10.0); + // Represents the last delegate provided to the last row and column + expect(columnExtent.delegate.precedingExtent, 900.0); + expect(columnExtent.delegate.viewportExtent, 800.0); + expect(rowExtent.delegate.precedingExtent, 900.0); + expect(rowExtent.delegate.viewportExtent, 600.0); + }); + + testWidgets('regular layout - no pinning', (WidgetTester tester) async { + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + final TableView tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 100, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + verticalDetails: ScrollableDetails.vertical( + controller: verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + controller: horizontalController, + ), + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 1 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 1'), findsOneWidget); + expect(find.text('Row: 1 Column: 1'), findsOneWidget); + // Within the cacheExtent + expect(find.text('Row: 3 Column: 0'), findsOneWidget); + expect(find.text('Row: 4 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 4'), findsOneWidget); + expect(find.text('Row: 1 Column: 5'), findsOneWidget); + // Outside of the cacheExtent + expect(find.text('Row: 10 Column: 10'), findsNothing); + expect(find.text('Row: 11 Column: 10'), findsNothing); + expect(find.text('Row: 10 Column: 11'), findsNothing); + expect(find.text('Row: 11 Column: 11'), findsNothing); + + // Let's scroll! + verticalController.jumpTo(verticalController.position.maxScrollExtent); + await tester.pump(); + expect(verticalController.position.pixels, 4400.0); + expect(horizontalController.position.pixels, 0.0); + expect(find.text('Row: 49 Column: 0'), findsOneWidget); + expect(find.text('Row: 48 Column: 0'), findsOneWidget); + expect(find.text('Row: 49 Column: 1'), findsOneWidget); + expect(find.text('Row: 48 Column: 1'), findsOneWidget); + // Within the CacheExtent + expect(find.text('Row: 49 Column: 4'), findsOneWidget); + expect(find.text('Row: 48 Column: 5'), findsOneWidget); + // Not around. + expect(find.text('Row: 0 Column: 0'), findsNothing); + expect(find.text('Row: 1 Column: 0'), findsNothing); + expect(find.text('Row: 0 Column: 1'), findsNothing); + expect(find.text('Row: 1 Column: 1'), findsNothing); + + // Let's scroll some more! + horizontalController.jumpTo(verticalController.position.maxScrollExtent); + await tester.pump(); + expect(verticalController.position.pixels, 4400.0); + expect(horizontalController.position.pixels, 4400.0); + expect(find.text('Row: 49 Column: 49'), findsOneWidget); + expect(find.text('Row: 48 Column: 49'), findsOneWidget); + expect(find.text('Row: 49 Column: 48'), findsOneWidget); + expect(find.text('Row: 48 Column: 48'), findsOneWidget); + // Nothing within the CacheExtent + expect(find.text('Row: 50 Column: 50'), findsNothing); + expect(find.text('Row: 51 Column: 51'), findsNothing); + // Not around. + expect(find.text('Row: 0 Column: 0'), findsNothing); + expect(find.text('Row: 1 Column: 0'), findsNothing); + expect(find.text('Row: 0 Column: 1'), findsNothing); + expect(find.text('Row: 1 Column: 1'), findsNothing); + }); + + testWidgets('pinned rows and columns', (WidgetTester tester) async { + // Just pinned rows + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + TableView tableView = TableView.builder( + rowCount: 50, + pinnedRowCount: 1, + columnCount: 50, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 100, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + verticalDetails: ScrollableDetails.vertical( + controller: verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + controller: horizontalController, + ), + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 1 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 1'), findsOneWidget); + expect(find.text('Row: 1 Column: 1'), findsOneWidget); + // Within the cacheExtent + expect(find.text('Row: 6 Column: 0'), findsOneWidget); + expect(find.text('Row: 7 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 8'), findsOneWidget); + expect(find.text('Row: 1 Column: 8'), findsOneWidget); + // Outside of the cacheExtent + expect(find.text('Row: 10 Column: 10'), findsNothing); + expect(find.text('Row: 11 Column: 10'), findsNothing); + expect(find.text('Row: 10 Column: 11'), findsNothing); + expect(find.text('Row: 11 Column: 11'), findsNothing); + + // Let's scroll! + verticalController.jumpTo(verticalController.position.maxScrollExtent); + await tester.pump(); + expect(verticalController.position.pixels, 4400.0); + expect(horizontalController.position.pixels, 0.0); + expect(find.text('Row: 49 Column: 0'), findsOneWidget); + expect(find.text('Row: 48 Column: 0'), findsOneWidget); + expect(find.text('Row: 49 Column: 1'), findsOneWidget); + expect(find.text('Row: 48 Column: 1'), findsOneWidget); + // Within the CacheExtent + expect(find.text('Row: 49 Column: 8'), findsOneWidget); + expect(find.text('Row: 48 Column: 9'), findsOneWidget); + // Not around unless pinned. + expect(find.text('Row: 0 Column: 0'), findsOneWidget); // Pinned row + expect(find.text('Row: 1 Column: 0'), findsNothing); + expect(find.text('Row: 0 Column: 1'), findsOneWidget); // Pinned row + expect(find.text('Row: 1 Column: 1'), findsNothing); + + // Let's scroll some more! + horizontalController.jumpTo(verticalController.position.maxScrollExtent); + await tester.pump(); + expect(verticalController.position.pixels, 4400.0); + expect(horizontalController.position.pixels, 4400.0); + expect(find.text('Row: 49 Column: 49'), findsOneWidget); + expect(find.text('Row: 48 Column: 49'), findsOneWidget); + expect(find.text('Row: 49 Column: 48'), findsOneWidget); + expect(find.text('Row: 48 Column: 48'), findsOneWidget); + // Nothing within the CacheExtent + expect(find.text('Row: 50 Column: 50'), findsNothing); + expect(find.text('Row: 51 Column: 51'), findsNothing); + // Not around unless pinned. + expect(find.text('Row: 0 Column: 49'), findsOneWidget); // Pinned row + expect(find.text('Row: 1 Column: 49'), findsNothing); + expect(find.text('Row: 0 Column: 48'), findsOneWidget); // Pinned row + expect(find.text('Row: 1 Column: 48'), findsNothing); + + // Just pinned columns + verticalController.jumpTo(0.0); + horizontalController.jumpTo(0.0); + tableView = TableView.builder( + rowCount: 50, + pinnedColumnCount: 1, + columnCount: 50, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 100, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + verticalDetails: ScrollableDetails.vertical( + controller: verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + controller: horizontalController, + ), + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 1 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 1'), findsOneWidget); + expect(find.text('Row: 1 Column: 1'), findsOneWidget); + // Within the cacheExtent + expect(find.text('Row: 6 Column: 0'), findsOneWidget); + expect(find.text('Row: 7 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 8'), findsOneWidget); + expect(find.text('Row: 1 Column: 9'), findsOneWidget); + // Outside of the cacheExtent + expect(find.text('Row: 10 Column: 10'), findsNothing); + expect(find.text('Row: 11 Column: 10'), findsNothing); + expect(find.text('Row: 10 Column: 11'), findsNothing); + expect(find.text('Row: 11 Column: 11'), findsNothing); + + // Let's scroll! + verticalController.jumpTo(verticalController.position.maxScrollExtent); + await tester.pump(); + expect(verticalController.position.pixels, 4400.0); + expect(horizontalController.position.pixels, 0.0); + expect(find.text('Row: 49 Column: 0'), findsOneWidget); + expect(find.text('Row: 48 Column: 0'), findsOneWidget); + expect(find.text('Row: 49 Column: 1'), findsOneWidget); + expect(find.text('Row: 48 Column: 1'), findsOneWidget); + // Within the CacheExtent + expect(find.text('Row: 49 Column: 8'), findsOneWidget); + expect(find.text('Row: 48 Column: 9'), findsOneWidget); + // Not around unless pinned. + expect(find.text('Row: 49 Column: 0'), findsOneWidget); // Pinned column + expect(find.text('Row: 48 Column: 0'), findsOneWidget); // Pinned column + expect(find.text('Row: 0 Column: 1'), findsNothing); + expect(find.text('Row: 1 Column: 1'), findsNothing); + + // Let's scroll some more! + horizontalController.jumpTo(verticalController.position.maxScrollExtent); + await tester.pump(); + expect(verticalController.position.pixels, 4400.0); + expect(horizontalController.position.pixels, 4400.0); + expect(find.text('Row: 49 Column: 49'), findsOneWidget); + expect(find.text('Row: 48 Column: 49'), findsOneWidget); + expect(find.text('Row: 49 Column: 48'), findsOneWidget); + expect(find.text('Row: 48 Column: 48'), findsOneWidget); + // Nothing within the CacheExtent + expect(find.text('Row: 50 Column: 50'), findsNothing); + expect(find.text('Row: 51 Column: 51'), findsNothing); + // Not around. + expect(find.text('Row: 49 Column: 0'), findsOneWidget); // Pinned column + expect(find.text('Row: 48 Column: 0'), findsOneWidget); // Pinned column + expect(find.text('Row: 0 Column: 1'), findsNothing); + expect(find.text('Row: 1 Column: 1'), findsNothing); + + // Both + verticalController.jumpTo(0.0); + horizontalController.jumpTo(0.0); + tableView = TableView.builder( + rowCount: 50, + pinnedColumnCount: 1, + pinnedRowCount: 1, + columnCount: 50, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 100, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + verticalDetails: ScrollableDetails.vertical( + controller: verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + controller: horizontalController, + ), + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + expect(find.text('Row: 0 Column: 0'), findsOneWidget); + expect(find.text('Row: 1 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 1'), findsOneWidget); + expect(find.text('Row: 1 Column: 1'), findsOneWidget); + // Within the cacheExtent + expect(find.text('Row: 7 Column: 0'), findsOneWidget); + expect(find.text('Row: 6 Column: 0'), findsOneWidget); + expect(find.text('Row: 0 Column: 8'), findsOneWidget); + expect(find.text('Row: 1 Column: 9'), findsOneWidget); + // Outside of the cacheExtent + expect(find.text('Row: 10 Column: 10'), findsNothing); + expect(find.text('Row: 11 Column: 10'), findsNothing); + expect(find.text('Row: 10 Column: 11'), findsNothing); + expect(find.text('Row: 11 Column: 11'), findsNothing); + + // Let's scroll! + verticalController.jumpTo(verticalController.position.maxScrollExtent); + await tester.pump(); + expect(verticalController.position.pixels, 4400.0); + expect(horizontalController.position.pixels, 0.0); + expect(find.text('Row: 49 Column: 0'), findsOneWidget); + expect(find.text('Row: 48 Column: 0'), findsOneWidget); + expect(find.text('Row: 49 Column: 1'), findsOneWidget); + expect(find.text('Row: 48 Column: 1'), findsOneWidget); + // Within the CacheExtent + expect(find.text('Row: 49 Column: 8'), findsOneWidget); + expect(find.text('Row: 48 Column: 9'), findsOneWidget); + // Not around unless pinned. + expect(find.text('Row: 0 Column: 0'), findsOneWidget); // Pinned + expect(find.text('Row: 48 Column: 0'), findsOneWidget); // Pinned + expect(find.text('Row: 0 Column: 1'), findsOneWidget); // Pinned + expect(find.text('Row: 1 Column: 1'), findsNothing); + + // Let's scroll some more! + horizontalController.jumpTo(verticalController.position.maxScrollExtent); + await tester.pump(); + expect(verticalController.position.pixels, 4400.0); + expect(horizontalController.position.pixels, 4400.0); + expect(find.text('Row: 49 Column: 49'), findsOneWidget); + expect(find.text('Row: 48 Column: 49'), findsOneWidget); + expect(find.text('Row: 49 Column: 48'), findsOneWidget); + expect(find.text('Row: 48 Column: 48'), findsOneWidget); + // Nothing within the CacheExtent + expect(find.text('Row: 50 Column: 50'), findsNothing); + expect(find.text('Row: 51 Column: 51'), findsNothing); + // Not around unless pinned. + expect(find.text('Row: 0 Column: 0'), findsOneWidget); // Pinned + expect(find.text('Row: 49 Column: 0'), findsOneWidget); // Pinned + expect(find.text('Row: 0 Column: 49'), findsOneWidget); // Pinned + expect(find.text('Row: 1 Column: 1'), findsNothing); + }); + + testWidgets('only paints visible cells', (WidgetTester tester) async { + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + final TableView tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + columnBuilder: (_) => span, + rowBuilder: (_) => span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 100, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + verticalDetails: ScrollableDetails.vertical( + controller: verticalController, + ), + horizontalDetails: ScrollableDetails.horizontal( + controller: horizontalController, + ), + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + + bool cellNeedsPaint(String cell) { + return find.text(cell).evaluate().first.renderObject!.debugNeedsPaint; + } + + expect(cellNeedsPaint('Row: 0 Column: 0'), isFalse); + expect(cellNeedsPaint('Row: 0 Column: 1'), isFalse); + expect(cellNeedsPaint('Row: 0 Column: 2'), isFalse); + expect(cellNeedsPaint('Row: 0 Column: 3'), isFalse); + expect(cellNeedsPaint('Row: 0 Column: 4'), isFalse); + expect(cellNeedsPaint('Row: 0 Column: 5'), isFalse); + expect(cellNeedsPaint('Row: 0 Column: 6'), isFalse); + expect(cellNeedsPaint('Row: 0 Column: 7'), isFalse); + expect(cellNeedsPaint('Row: 0 Column: 8'), isTrue); // cacheExtent + expect(cellNeedsPaint('Row: 0 Column: 9'), isTrue); // cacheExtent + expect(cellNeedsPaint('Row: 0 Column: 10'), isTrue); // cacheExtent + expect( + find.text('Row: 0 Column: 11'), + findsNothing, + ); // outside of cacheExtent + + expect(cellNeedsPaint('Row: 1 Column: 0'), isFalse); + expect(cellNeedsPaint('Row: 2 Column: 0'), isFalse); + expect(cellNeedsPaint('Row: 3 Column: 0'), isFalse); + expect(cellNeedsPaint('Row: 4 Column: 0'), isFalse); + expect(cellNeedsPaint('Row: 5 Column: 0'), isFalse); + expect(cellNeedsPaint('Row: 6 Column: 0'), isTrue); // cacheExtent + expect(cellNeedsPaint('Row: 7 Column: 0'), isTrue); // cacheExtent + expect(cellNeedsPaint('Row: 8 Column: 0'), isTrue); // cacheExtent + expect( + find.text('Row: 9 Column: 0'), + findsNothing, + ); // outside of cacheExtent + + // Check a couple other cells + expect(cellNeedsPaint('Row: 5 Column: 7'), isFalse); // last visible cell + expect(cellNeedsPaint('Row: 6 Column: 7'), isTrue); // also in cacheExtent + expect(cellNeedsPaint('Row: 5 Column: 8'), isTrue); // also in cacheExtent + expect(cellNeedsPaint('Row: 6 Column: 8'), isTrue); // also in cacheExtent + }); + + testWidgets('paints decorations in correct order', + (WidgetTester tester) async { + // TODO(Piinks): Rewrite this to remove golden files from this repo when + // mock_canvas is public - https://github.com/flutter/flutter/pull/131631 + // foreground, background, and precedence per mainAxis + TableView tableView = TableView.builder( + rowCount: 2, + columnCount: 2, + columnBuilder: (int index) => TableSpan( + extent: const FixedTableSpanExtent(200.0), + foregroundDecoration: const TableSpanDecoration( + border: TableSpanBorder( + trailing: BorderSide( + color: Colors.orange, + width: 3, + ))), + backgroundDecoration: TableSpanDecoration( + color: index.isEven ? Colors.red : null, + ), + ), + rowBuilder: (int index) => TableSpan( + extent: const FixedTableSpanExtent(200.0), + foregroundDecoration: const TableSpanDecoration( + border: TableSpanBorder( + leading: BorderSide( + color: Colors.green, + width: 3, + ))), + backgroundDecoration: TableSpanDecoration( + color: index.isOdd ? Colors.blue : null, + ), + ), + cellBuilder: (_, TableVicinity vicinity) { + return const SizedBox.square( + dimension: 200, + child: Center(child: FlutterLogo()), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + await expectLater( + find.byType(TableView), + matchesGoldenFile('goldens/tableSpanDecoration.defaultMainAxis.png'), + skip: !runGoldens, + ); + + // Switch main axis + tableView = TableView.builder( + mainAxis: Axis.horizontal, + rowCount: 2, + columnCount: 2, + columnBuilder: (int index) => TableSpan( + extent: const FixedTableSpanExtent(200.0), + foregroundDecoration: const TableSpanDecoration( + border: TableSpanBorder( + trailing: BorderSide( + color: Colors.orange, + width: 3, + ))), + backgroundDecoration: TableSpanDecoration( + color: index.isEven ? Colors.red : null, + ), + ), + rowBuilder: (int index) => TableSpan( + extent: const FixedTableSpanExtent(200.0), + foregroundDecoration: const TableSpanDecoration( + border: TableSpanBorder( + leading: BorderSide( + color: Colors.green, + width: 3, + ))), + backgroundDecoration: TableSpanDecoration( + color: index.isOdd ? Colors.blue : null, + ), + ), + cellBuilder: (_, TableVicinity vicinity) { + return const SizedBox.square( + dimension: 200, + child: Center(child: FlutterLogo()), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + await expectLater( + find.byType(TableView), + matchesGoldenFile('goldens/tableSpanDecoration.horizontalMainAxis.png'), + skip: !runGoldens, + ); + }); + + testWidgets('mouse handling', (WidgetTester tester) async { + int enterCounter = 0; + int exitCounter = 0; + final TableView tableView = TableView.builder( + rowCount: 50, + columnCount: 50, + columnBuilder: (_) => span, + rowBuilder: (int index) => index.isEven + ? getMouseTrackingSpan( + index, + onEnter: (_) => enterCounter++, + onExit: (_) => exitCounter++, + ) + : span, + cellBuilder: (_, TableVicinity vicinity) { + return SizedBox.square( + dimension: 100, + child: Text('Row: ${vicinity.row} Column: ${vicinity.column}'), + ); + }, + ); + + await tester.pumpWidget(MaterialApp(home: tableView)); + await tester.pumpAndSettle(); + // Even rows will respond to mouse, odd will not + final Offset evenRow = tester.getCenter(find.text('Row: 2 Column: 2')); + final Offset oddRow = tester.getCenter(find.text('Row: 3 Column: 2')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(location: oddRow); + expect(enterCounter, 0); + expect(exitCounter, 0); + expect( + RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), + SystemMouseCursors.basic, + ); + await gesture.moveTo(evenRow); + await tester.pumpAndSettle(); + expect(enterCounter, 1); + expect(exitCounter, 0); + expect( + RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), + SystemMouseCursors.cell, + ); + await gesture.moveTo(oddRow); + await tester.pumpAndSettle(); + expect(enterCounter, 1); + expect(exitCounter, 1); + expect( + RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), + SystemMouseCursors.basic, + ); + }); + }); +} + +class _NullBuildContext implements BuildContext, TwoDimensionalChildManager { + @override + dynamic noSuchMethod(Invocation invocation) => throw UnimplementedError(); +} + +RenderTableViewport getViewport(WidgetTester tester, Key childKey) { + return RenderAbstractViewport.of(tester.renderObject(find.byKey(childKey))) + as RenderTableViewport; +} + +class TestTableSpanExtent extends TableSpanExtent { + late TableSpanExtentDelegate delegate; + + @override + double calculateExtent(TableSpanExtentDelegate delegate) { + this.delegate = delegate; + return 100; + } +}