diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 718328b1f8..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,40 +0,0 @@ -version: 2.1 -orbs: - codecov: codecov/codecov@1.0.2 -executors: - default-executor: - docker: - - image: cirrusci/flutter:stable - resource_class: large - shell: /bin/bash -jobs: - build: - executor: default-executor - steps: - - checkout - - run: flutter --version - - run: - name: Set up environment - command: | - echo 'export PATH=$HOME/.pub-cache/bin:$PATH' >> $BASH_ENV - source $BASH_ENV - - run: - name: Setup melos - command: | - flutter pub global activate melos - melos --version - melos bootstrap - - run: - name: Run Test Suite - command: melos run test - - run: - name: Generate Coverage Report - command: melos run gen_coverage - - codecov/upload: - file: coverage_report/lcov.info - - run: - name: Run flutter analyze - command: melos run analyze - - run: - name: Check That Flutter Code is Formatted Correctly - command: dart format -o none --set-exit-if-changed . diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 6418d1dee6..6627ba6868 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +github: Sub6Resources patreon: # Replace with a single Patreon username open_collective: flutter_html ko_fi: # Replace with a single Ko-fi username diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..3bd2e1fd11 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,33 @@ +name: flutter_html tests + +on: + pull_request: + branches: [ master ] + push: + branches: [ master ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dart-lang/setup-dart@v1 + - uses: flutter-actions/setup-flutter@54feb1e258158303e041b9eaf89314dcfbf6d38a + - name: Setup Melos + run: flutter pub global activate melos + - name: Bootstrap Project + run: melos bootstrap + - name: Run Test Suite + run: flutter pub global run melos run test + - name: Compile Test Coverage Report + run: flutter pub global run melos run gen_coverage + - name: Upload Coverage to Codecov + uses: codecov/codecov-action@v5 + with: + files: coverage_report/lcov.info + disable_search: true + token: ${{ secrets.CODECOV_TOKEN }} + - name: Run Dart Analysis + run: flutter pub global run melos analyze --fatal-infos + - name: Check that `dart format` has been run on every file + run: dart format -o none --set-exit-if-changed . diff --git a/CHANGELOG.md b/CHANGELOG.md index 118b64133b..13a7fda044 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,79 +3,13 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -## 2023-05-31 +#### 3.0.0 *March 2025* -### Changes + - Graduate package to a stable release. See pre-releases prior to this version for changelog entries. ---- +#### 3.0.0-beta.2 *May 2023* -Packages with breaking changes: - - - There are no breaking changes in this release. - -Packages with other changes: - - - [`flutter_html` - `v3.0.0-beta.2`](#flutter_html---v300-beta2) - - [`flutter_html_audio` - `v3.0.0-beta.2`](#flutter_html_audio---v300-beta2) - - [`flutter_html_iframe` - `v3.0.0-beta.2`](#flutter_html_iframe---v300-beta2) - - [`flutter_html_math` - `v3.0.0-beta.2`](#flutter_html_math---v300-beta2) - - [`flutter_html_svg` - `v3.0.0-beta.2`](#flutter_html_svg---v300-beta2) - - [`flutter_html_table` - `v3.0.0-beta.2`](#flutter_html_table---v300-beta2) - - [`flutter_html_video` - `v3.0.0-beta.2`](#flutter_html_video---v300-beta2) - - [`flutter_html_all` - `v3.0.0-beta.2`](#flutter_html_all---v300-beta2) - -Packages with dependency updates only: - -> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. - - - `flutter_html_all` - `v3.0.0-beta.2` - ---- - -#### `flutter_html` - `v3.0.0-beta.2` - - - **FIX**: start list items on a new line ([#1281](https://github.com/sub6resources/flutter_html/issues/1281)). ([496d1aa8](https://github.com/sub6resources/flutter_html/commit/496d1aa8e655891d2f597c5e4d7e92057801d815)) - - **FIX**: Add "display: Display.block" to table ([#1278](https://github.com/sub6resources/flutter_html/issues/1278)). ([6350f023](https://github.com/sub6resources/flutter_html/commit/6350f02354b7de601ce294123717e2051be97eee)) - - **FIX**: improve API for ExtensionContext and export marker.dart ([#1273](https://github.com/sub6resources/flutter_html/issues/1273)). ([27e33a95](https://github.com/sub6resources/flutter_html/commit/27e33a955e872d47306db9480f74f6da2e9a028a)) - - **FIX**: Cleaned up whitespace processing and added whitespace tests ([#1267](https://github.com/sub6resources/flutter_html/issues/1267)). ([cc00406b](https://github.com/sub6resources/flutter_html/commit/cc00406b1d0c115e5c66dd4bdfb40db32496f55f)) - - **FIX**: a tag should not style as link if href is not provided ([#1265](https://github.com/sub6resources/flutter_html/issues/1265)). ([d7247cb3](https://github.com/sub6resources/flutter_html/commit/d7247cb303c25d0011f85f9b2d3687924de3d83d)) - - **FEAT**: Update CssBoxWidget to handle rtl marker boxes ([#1270](https://github.com/sub6resources/flutter_html/issues/1270)). ([d7091990](https://github.com/sub6resources/flutter_html/commit/d7091990d193e892e2f782ac8d91fc0326aff4bc)) - - **FEAT**: support vertical-align in inline styles ([#1266](https://github.com/sub6resources/flutter_html/issues/1266)). ([fe896de5](https://github.com/sub6resources/flutter_html/commit/fe896de5ed8b79425bb33800a26fa4ac328057fe)) - - **FEAT**: Add WrapperExtension helper ([#1264](https://github.com/sub6resources/flutter_html/issues/1264)). ([2ffa1dda](https://github.com/sub6resources/flutter_html/commit/2ffa1ddabb3f2a660ab85c551255b89fe8a24ab5)) - -#### `flutter_html_audio` - `v3.0.0-beta.2` - - - **FIX**: improve API for ExtensionContext and export marker.dart ([#1273](https://github.com/sub6resources/flutter_html/issues/1273)). ([27e33a95](https://github.com/sub6resources/flutter_html/commit/27e33a955e872d47306db9480f74f6da2e9a028a)) - - **FEAT**: Add WrapperExtension helper ([#1264](https://github.com/sub6resources/flutter_html/issues/1264)). ([2ffa1dda](https://github.com/sub6resources/flutter_html/commit/2ffa1ddabb3f2a660ab85c551255b89fe8a24ab5)) - -#### `flutter_html_iframe` - `v3.0.0-beta.2` - - - **FIX**: improve API for ExtensionContext and export marker.dart ([#1273](https://github.com/sub6resources/flutter_html/issues/1273)). ([27e33a95](https://github.com/sub6resources/flutter_html/commit/27e33a955e872d47306db9480f74f6da2e9a028a)) - - **FEAT**: Add WrapperExtension helper ([#1264](https://github.com/sub6resources/flutter_html/issues/1264)). ([2ffa1dda](https://github.com/sub6resources/flutter_html/commit/2ffa1ddabb3f2a660ab85c551255b89fe8a24ab5)) - -#### `flutter_html_math` - `v3.0.0-beta.2` - - - **FIX**: improve API for ExtensionContext and export marker.dart ([#1273](https://github.com/sub6resources/flutter_html/issues/1273)). ([27e33a95](https://github.com/sub6resources/flutter_html/commit/27e33a955e872d47306db9480f74f6da2e9a028a)) - - **FEAT**: Add WrapperExtension helper ([#1264](https://github.com/sub6resources/flutter_html/issues/1264)). ([2ffa1dda](https://github.com/sub6resources/flutter_html/commit/2ffa1ddabb3f2a660ab85c551255b89fe8a24ab5)) - -#### `flutter_html_svg` - `v3.0.0-beta.2` - - - **FIX**: improve API for ExtensionContext and export marker.dart ([#1273](https://github.com/sub6resources/flutter_html/issues/1273)). ([27e33a95](https://github.com/sub6resources/flutter_html/commit/27e33a955e872d47306db9480f74f6da2e9a028a)) - - **FEAT**: Add WrapperExtension helper ([#1264](https://github.com/sub6resources/flutter_html/issues/1264)). ([2ffa1dda](https://github.com/sub6resources/flutter_html/commit/2ffa1ddabb3f2a660ab85c551255b89fe8a24ab5)) - -#### `flutter_html_table` - `v3.0.0-beta.2` - - - **FIX**: Add "display: Display.block" to table ([#1278](https://github.com/sub6resources/flutter_html/issues/1278)). ([6350f023](https://github.com/sub6resources/flutter_html/commit/6350f02354b7de601ce294123717e2051be97eee)) - - **FIX**: improve API for ExtensionContext and export marker.dart ([#1273](https://github.com/sub6resources/flutter_html/issues/1273)). ([27e33a95](https://github.com/sub6resources/flutter_html/commit/27e33a955e872d47306db9480f74f6da2e9a028a)) - - **FEAT**: support vertical-align in inline styles ([#1266](https://github.com/sub6resources/flutter_html/issues/1266)). ([fe896de5](https://github.com/sub6resources/flutter_html/commit/fe896de5ed8b79425bb33800a26fa4ac328057fe)) - - **FEAT**: Add WrapperExtension helper ([#1264](https://github.com/sub6resources/flutter_html/issues/1264)). ([2ffa1dda](https://github.com/sub6resources/flutter_html/commit/2ffa1ddabb3f2a660ab85c551255b89fe8a24ab5)) - -#### `flutter_html_video` - `v3.0.0-beta.2` - - - **FIX**: improve API for ExtensionContext and export marker.dart ([#1273](https://github.com/sub6resources/flutter_html/issues/1273)). ([27e33a95](https://github.com/sub6resources/flutter_html/commit/27e33a955e872d47306db9480f74f6da2e9a028a)) - - **FEAT**: Add WrapperExtension helper ([#1264](https://github.com/sub6resources/flutter_html/issues/1264)). ([2ffa1dda](https://github.com/sub6resources/flutter_html/commit/2ffa1ddabb3f2a660ab85c551255b89fe8a24ab5)) - -## 3.0.0-beta.2 + - Several Breaking Changes. See the [migration guide](https://github.com/Sub6Resources/flutter_html/wiki/Migration-Guides#300) - **FIX**: start list items on a new line ([#1281](https://github.com/sub6resources/flutter_html/issues/1281)). ([496d1aa8](https://github.com/sub6resources/flutter_html/commit/496d1aa8e655891d2f597c5e4d7e92057801d815)) - **FIX**: Add "display: Display.block" to table ([#1278](https://github.com/sub6resources/flutter_html/issues/1278)). ([6350f023](https://github.com/sub6resources/flutter_html/commit/6350f02354b7de601ce294123717e2051be97eee)) @@ -86,8 +20,6 @@ Packages with dependency updates only: - **FEAT**: support vertical-align in inline styles ([#1266](https://github.com/sub6resources/flutter_html/issues/1266)). ([fe896de5](https://github.com/sub6resources/flutter_html/commit/fe896de5ed8b79425bb33800a26fa4ac328057fe)) - **FEAT**: Add WrapperExtension helper ([#1264](https://github.com/sub6resources/flutter_html/issues/1264)). ([2ffa1dda](https://github.com/sub6resources/flutter_html/commit/2ffa1ddabb3f2a660ab85c551255b89fe8a24ab5)) -# Change Log - #### 3.0.0-beta.1 - *May 2023* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2dc1383211..02466657d6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,3 @@ Thanks for your interest in contributing to `flutter_html`! -I'm pretty busy, so in order to help me best make use of the time I spend working on this project, please adhere to the following guidelines when contributing: - -1. In general, don't submit a pull request without discussing the feature with me, in an issue, first. I don't want you to have to do a whole bunch of work for nothing. This also makes it so there won't be a whole bunch of changes I make you do in order to have your pull request merged. -2. Please read the [wiki](https://github.com/Sub6Resources/flutter_html/wiki) before contributing (there are only two pages at the moment). This will give you an idea of what my plans are for this repository, and what you do and don't need to work on. - -More specific guidelines will be added soon. +Please see the contribution guide in the wiki: https://github.com/Sub6Resources/flutter_html/wiki/Contributing diff --git a/LICENSE b/LICENSE index 89971b33a6..230b35b47f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2022 The flutter_html developers +Copyright (c) 2019-2025 The flutter_html developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 7cb73ac0fe..adea04a977 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # flutter_html [![pub package](https://img.shields.io/pub/v/flutter_html.svg)](https://pub.dev/packages/flutter_html) [![codecov](https://codecov.io/gh/Sub6Resources/flutter_html/branch/master/graph/badge.svg)](https://codecov.io/gh/Sub6Resources/flutter_html) -[![CircleCI](https://circleci.com/gh/Sub6Resources/flutter_html.svg?style=svg)](https://circleci.com/gh/Sub6Resources/flutter_html) +[![GitHub Actions](https://github.com/Sub6Resources/flutter_html/actions/workflows/test.yml/badge.svg)](https://github.com/Sub6Resources/flutter_html/actions) [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/Sub6Resources/flutter_html/blob/master/LICENSE) A Flutter widget for rendering HTML and CSS as Flutter widgets. @@ -143,7 +143,7 @@ Add the dependency to your pubspec.yaml: flutter pub add flutter_html_audio ```dart -import 'package:flutter_html_audio/flutter_html_audio'; +import 'package:flutter_html_audio/flutter_html_audio.dart'; Widget html = Html( data: myHtml, @@ -168,7 +168,7 @@ Add the dependency to your pubspec.yaml: flutter pub add flutter_html_iframe ```dart -import 'package:flutter_html_iframe/flutter_html_iframe'; +import 'package:flutter_html_iframe/flutter_html_iframe.dart'; Widget html = Html( data: myHtml, @@ -195,7 +195,7 @@ Add the dependency to your pubspec.yaml: flutter pub add flutter_html_math ```dart -import 'package:flutter_html_math/flutter_html_math'; +import 'package:flutter_html_math/flutter_html_math.dart'; Widget html = Html( data: myHtml, @@ -256,7 +256,7 @@ Add the dependency to your pubspec.yaml: flutter pub add flutter_html_svg ```dart -import 'package:flutter_html_svg/flutter_html_svg'; +import 'package:flutter_html_svg/flutter_html_svg.dart'; Widget html = Html( data: myHtml, @@ -279,7 +279,7 @@ Add the dependency to your pubspec.yaml: flutter pub add flutter_html_table ```dart -import 'package:flutter_html_table/flutter_html_table'; +import 'package:flutter_html_table/flutter_html_table.dart'; Widget html = Html( data: myHtml, @@ -302,7 +302,7 @@ Add the dependency to your pubspec.yaml: flutter pub add flutter_html_video ```dart -import 'package:flutter_html_video/flutter_html_video'; +import 'package:flutter_html_video/flutter_html_video.dart'; Widget html = Html( data: myHtml, diff --git a/example/.metadata b/example/.metadata index e0236519dc..b02a7e4bf2 100644 --- a/example/.metadata +++ b/example/.metadata @@ -4,7 +4,42 @@ # This file should be version controlled and should not be manually edited. version: - revision: 20e59316b8b8474554b38493b8ca888794b0234a - channel: stable + revision: "17025dd88227cd9532c33fa78f5250d548d87e9a" + channel: "stable" project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: android + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: ios + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: linux + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: macos + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: web + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: windows + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + + # 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/example/android/.gitignore b/example/android/.gitignore new file mode 100644 index 0000000000..55afd919c6 --- /dev/null +++ b/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/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000000..399f6981d5 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 0000000000..70f8f08f24 --- /dev/null +++ b/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000000..f74085f3f6 --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000000..06952be745 --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000000..399f6981d5 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/ios/.gitignore b/example/ios/.gitignore new file mode 100644 index 0000000000..7a7f9873ad --- /dev/null +++ b/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/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000..f9b0d7c5ea --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000..f9b0d7c5ea --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000000..86a7c3b1b6 --- /dev/null +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +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/example/lib/main.dart b/example/lib/main.dart index cb9f545ad8..893908f737 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -21,7 +21,7 @@ class MyApp extends StatelessWidget { } class MyHomePage extends StatefulWidget { - const MyHomePage({Key? key, required this.title}) : super(key: key); + const MyHomePage({super.key, required this.title}); final String title; @@ -37,20 +37,20 @@ const htmlData = r"""

Header 4

Header 5
Header 6
- +

Inline Styles:

The should be BLUE style='color: blue;'

The should be RED style='color: red;'

The should be BLACK with 10% alpha style='color: rgba(0, 0, 0, 0.10);

The should be GREEN style='color: rgb(0, 97, 0);

The should be GREEN style='color: rgb(0, 97, 0);

- +

Text Alignment

Center Aligned Text

Right Aligned Text

Justified Text

Center Aligned Text

- +

Margins

Default Div (width 350px height 20px)
margin-left: 3em
@@ -59,17 +59,17 @@ const htmlData = r"""
margin-left: auto
margin-right: auto
margin-left: auto; margin-right: 3em
- +

Margin Auto on Image

display:inline-block; margin: auto; (should not center):

- +

display:block margin: auto; (should center):

- - + +

Support for sub/sup

Solve for xn: log2(x2+n) = 93

One of the most common equations in all of physics is
E=mc2.

- +

Ruby Support:

@@ -78,11 +78,11 @@ const htmlData = r"""  is Japanese Kanji.

- +

Support for maxLines:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vestibulum sapien feugiat lorem tempor, id porta orci elementum. Fusce sed justo id arcu egestas congue. Fusce tincidunt lacus ipsum, in imperdiet felis ultricies eu. In ullamcorper risus felis, ac maximus dui bibendum vel. Integer ligula tortor, facilisis eu mauris ut, ultrices hendrerit ex. Donec scelerisque massa consequat, eleifend mauris eu, mollis dui. Donec placerat augue tortor, et tincidunt quam tempus non. Quisque sagittis enim nisi, eu condimentum lacus egestas ac. Nam facilisis luctus ipsum, at aliquam urna fermentum a. Quisque tortor dui, faucibus in ante eget, pellentesque mattis nibh. In augue dolor, euismod vitae eleifend nec, tempus vel urna. Donec vitae augue accumsan ligula fringilla ultrices et vel ex.
- - + +

Table support (With custom styling!):

@@ -104,7 +104,7 @@ const htmlData = r"""
fDatafDatafData
- +

List support:

  1. This
  2. @@ -131,14 +131,14 @@ const htmlData = r"""
  3. Header 2

  4. Header 2
- +

Link support:

Linking to websites has never been easier.

- +

Image support:

- + @@ -151,23 +151,23 @@ const htmlData = r"""
Network pngxkcd
Local asset png
Custom image render
Broken network imageBroken network image alt text
- +

SVG support:

- +

Custom Element Support:

Inline: <bird></bird> becomes: .
- + Block: <flutter></flutter> becomes: and <flutter horizontal></flutter> becomes: - +

MathML Support:

@@ -261,7 +261,7 @@ const htmlData = r""" = 1 - +

Tex Support with the custom tex tag:

i\hbar\frac{\partial}{\partial t}\Psi(\vec x,t) = -\frac{\hbar}{2m}\nabla^2\Psi(\vec x,t)+ V(\vec x)\Psi(\vec x,t)

Scroll to top

diff --git a/example/linux/.gitignore b/example/linux/.gitignore new file mode 100644 index 0000000000..d3896c9844 --- /dev/null +++ b/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/example/linux/CMakeLists.txt b/example/linux/CMakeLists.txt new file mode 100644 index 0000000000..7a9a314f92 --- /dev/null +++ b/example/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +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) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# 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) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# 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/example/linux/flutter/CMakeLists.txt b/example/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000000..d5bd01648a --- /dev/null +++ b/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/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000000..e71a16d23d --- /dev/null +++ b/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/example/linux/flutter/generated_plugin_registrant.h b/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000000..e0f0a47bc0 --- /dev/null +++ b/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000000..2e1de87a7e --- /dev/null +++ b/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/example/linux/runner/CMakeLists.txt b/example/linux/runner/CMakeLists.txt new file mode 100644 index 0000000000..e97dabc702 --- /dev/null +++ b/example/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +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} + "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 preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/example/linux/runner/main.cc b/example/linux/runner/main.cc new file mode 100644 index 0000000000..e7c5c54370 --- /dev/null +++ b/example/linux/runner/main.cc @@ -0,0 +1,6 @@ +#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/example/linux/runner/my_application.cc b/example/linux/runner/my_application.cc new file mode 100644 index 0000000000..6c81082380 --- /dev/null +++ b/example/linux/runner/my_application.cc @@ -0,0 +1,130 @@ +#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 GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// 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_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/example/linux/runner/my_application.h b/example/linux/runner/my_application.h new file mode 100644 index 0000000000..72271d5e41 --- /dev/null +++ b/example/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#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/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 1c2c9b0787..2671e8141a 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,14 @@ import FlutterMacOS import Foundation -import wakelock_macos +import package_info_plus +import video_player_avfoundation +import wakelock_plus +import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) + WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) + WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) } diff --git a/example/macos/Podfile b/example/macos/Podfile index dade8dfad0..049abe2954 100644 --- a/example/macos/Podfile +++ b/example/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.11' +platform :osx, '10.14' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 6f0fb6010c..389bee53bf 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -27,6 +27,7 @@ 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 5F522694C62AF8897C0CC60C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 504E42C3FE1FD01681D066A3 /* Pods_Runner.framework */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -80,6 +81,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, 5F522694C62AF8897C0CC60C /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -175,6 +177,9 @@ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( @@ -200,10 +205,13 @@ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { @@ -256,6 +264,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -404,7 +413,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -483,7 +492,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -530,7 +539,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -627,6 +636,18 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d97..6172342eec 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/example/macos/Runner/DebugProfile.entitlements b/example/macos/Runner/DebugProfile.entitlements index dddb8a30c8..c946719a1a 100644 --- a/example/macos/Runner/DebugProfile.entitlements +++ b/example/macos/Runner/DebugProfile.entitlements @@ -8,5 +8,7 @@ com.apple.security.network.server + com.apple.security.network.client + diff --git a/example/macos/RunnerTests/RunnerTests.swift b/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000000..61f3bd1fc5 --- /dev/null +++ b/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +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/example/pubspec.yaml b/example/pubspec.yaml index 71e0332d40..a619c577f2 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,20 +4,18 @@ publish_to: none version: 1.0.0+1 environment: - sdk: '>=2.17.0 <3.0.0' + sdk: '>=3.2.0 <4.0.0' dependencies: - flutter_html: - path: .. - flutter_html_all: - path: ../packages/flutter_html_all + flutter_html: ^3.0.0 + flutter_html_all: ^3.0.0 flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.1 + flutter_lints: ^5.0.0 flutter: diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 0000000000..e900dfd40f --- /dev/null +++ b/example/test/widget_test.dart @@ -0,0 +1,7 @@ +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('Dummy test', () { + expect(2 + 2, equals(4)); + }); +} diff --git a/example/web/icons/Icon-maskable-192.png b/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000..eb9b4d76e5 Binary files /dev/null and b/example/web/icons/Icon-maskable-192.png differ diff --git a/example/web/icons/Icon-maskable-512.png b/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000..d69c56691f Binary files /dev/null and b/example/web/icons/Icon-maskable-512.png differ diff --git a/example/windows/.gitignore b/example/windows/.gitignore new file mode 100644 index 0000000000..d492d0d98c --- /dev/null +++ b/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/example/windows/CMakeLists.txt b/example/windows/CMakeLists.txt new file mode 100644 index 0000000000..d960948af6 --- /dev/null +++ b/example/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# 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() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# 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/example/windows/flutter/CMakeLists.txt b/example/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000000..903f4899d6 --- /dev/null +++ b/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# 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") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === 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" + ${FLUTTER_TARGET_PLATFORM} $ + 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/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000000..8b6d4680af --- /dev/null +++ b/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/example/windows/flutter/generated_plugin_registrant.h b/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000000..dc139d85a9 --- /dev/null +++ b/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000000..b93c4c30c1 --- /dev/null +++ b/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/example/windows/runner/CMakeLists.txt b/example/windows/runner/CMakeLists.txt new file mode 100644 index 0000000000..394917c053 --- /dev/null +++ b/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/example/windows/runner/Runner.rc b/example/windows/runner/Runner.rc new file mode 100644 index 0000000000..289fc7ee69 --- /dev/null +++ b/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) 2025 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/example/windows/runner/flutter_window.cpp b/example/windows/runner/flutter_window.cpp new file mode 100644 index 0000000000..955ee3038f --- /dev/null +++ b/example/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#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/example/windows/runner/flutter_window.h b/example/windows/runner/flutter_window.h new file mode 100644 index 0000000000..6da0652f05 --- /dev/null +++ b/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#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/example/windows/runner/main.cpp b/example/windows/runner/main.cpp new file mode 100644 index 0000000000..a61bf80d31 --- /dev/null +++ b/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#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/example/windows/runner/resource.h b/example/windows/runner/resource.h new file mode 100644 index 0000000000..66a65d1e4a --- /dev/null +++ b/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/example/windows/runner/resources/app_icon.ico b/example/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000..c04e20caf6 Binary files /dev/null and b/example/windows/runner/resources/app_icon.ico differ diff --git a/example/windows/runner/runner.exe.manifest b/example/windows/runner/runner.exe.manifest new file mode 100644 index 0000000000..153653e8d6 --- /dev/null +++ b/example/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/example/windows/runner/utils.cpp b/example/windows/runner/utils.cpp new file mode 100644 index 0000000000..3a0b46511a --- /dev/null +++ b/example/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#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(); + } + unsigned 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/example/windows/runner/utils.h b/example/windows/runner/utils.h new file mode 100644 index 0000000000..3879d54755 --- /dev/null +++ b/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#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/example/windows/runner/win32_window.cpp b/example/windows/runner/win32_window.cpp new file mode 100644 index 0000000000..60608d0fe5 --- /dev/null +++ b/example/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#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/example/windows/runner/win32_window.h b/example/windows/runner/win32_window.h new file mode 100644 index 0000000000..e901dde684 --- /dev/null +++ b/example/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#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/lib/flutter_html.dart b/lib/flutter_html.dart index 0247d4c94b..044e5e1ec3 100644 --- a/lib/flutter_html.dart +++ b/lib/flutter_html.dart @@ -1,4 +1,4 @@ -library flutter_html; +library; import 'package:flutter/material.dart'; import 'package:flutter_html/src/html_parser.dart'; @@ -10,6 +10,7 @@ import 'package:html/dom.dart' as dom; export 'package:flutter_html/src/html_parser.dart'; //export src for advanced custom render uses (e.g. casting context.tree) export 'package:flutter_html/src/anchor.dart'; +export 'package:flutter_html/src/tree/image_element.dart'; export 'package:flutter_html/src/tree/interactable_element.dart'; export 'package:flutter_html/src/tree/replaced_element.dart'; export 'package:flutter_html/src/tree/styled_element.dart'; @@ -47,7 +48,7 @@ class Html extends StatefulWidget { /// **style** Pass in the style information for the Html here. /// See [its wiki page](https://github.com/Sub6Resources/flutter_html/wiki/Style) for more info. Html({ - Key? key, + super.key, GlobalKey? anchorKey, required this.data, this.onLinkTap, @@ -60,13 +61,12 @@ class Html extends StatefulWidget { this.style = const {}, }) : documentElement = null, assert(data != null), - _anchorKey = anchorKey ?? GlobalKey(), - super(key: key); + _anchorKey = anchorKey ?? GlobalKey(); Html.fromDom({ - Key? key, + super.key, GlobalKey? anchorKey, - @required dom.Document? document, + required dom.Document? document, this.onLinkTap, this.onAnchorTap, this.extensions = const [], @@ -78,13 +78,12 @@ class Html extends StatefulWidget { }) : data = null, assert(document != null), documentElement = document!.documentElement, - _anchorKey = anchorKey ?? GlobalKey(), - super(key: key); + _anchorKey = anchorKey ?? GlobalKey(); Html.fromElement({ - Key? key, + super.key, GlobalKey? anchorKey, - @required this.documentElement, + required this.documentElement, this.onLinkTap, this.onAnchorTap, this.extensions = const [], @@ -95,8 +94,7 @@ class Html extends StatefulWidget { this.style = const {}, }) : data = null, assert(documentElement != null), - _anchorKey = anchorKey ?? GlobalKey(), - super(key: key); + _anchorKey = anchorKey ?? GlobalKey(); /// A unique key for this Html widget to ensure uniqueness of anchors final GlobalKey _anchorKey; @@ -126,7 +124,7 @@ class Html extends StatefulWidget { /// A set of the only HTML tags that should be rendered by this widget. /// - /// Note that the html parser wraps your html in an and tag + /// Note that the html parser wraps your html in an `` and `` tag /// by default, so you should include those in this set if you want any /// of your html to render. final Set? onlyRenderTheseTags; diff --git a/lib/src/builtins/image_builtin.dart b/lib/src/builtins/image_builtin.dart index 08bfd1b70d..d07b3051a3 100644 --- a/lib/src/builtins/image_builtin.dart +++ b/lib/src/builtins/image_builtin.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; -import 'package:flutter_html/src/tree/image_element.dart'; class ImageBuiltIn extends HtmlExtension { final String? dataEncoding; @@ -93,6 +92,9 @@ class ImageBuiltIn extends HtmlExtension { } return WidgetSpan( + alignment: context.style!.verticalAlign + .toPlaceholderAlignment(context.style!.display), + baseline: TextBaseline.alphabetic, child: CssBoxWidget( style: imageStyle, childIsReplaced: true, diff --git a/lib/src/builtins/interactive_element_builtin.dart b/lib/src/builtins/interactive_element_builtin.dart index e8486b0701..f0f052676c 100644 --- a/lib/src/builtins/interactive_element_builtin.dart +++ b/lib/src/builtins/interactive_element_builtin.dart @@ -30,6 +30,7 @@ class InteractiveElementBuiltIn extends HtmlExtension { style: Style( color: Colors.blue, textDecoration: TextDecoration.underline, + textDecorationColor: Colors.blue, ), node: context.node, elementId: context.id, @@ -61,12 +62,21 @@ class InteractiveElementBuiltIn extends HtmlExtension { children: childSpan.children ?.map((e) => _processInteractableChild(context, e)) .toList(), - style: childSpan.style, - semanticsLabel: childSpan.semanticsLabel, recognizer: TapGestureRecognizer()..onTap = onTap, + style: + context.styledElement?.style.generateTextStyle() ?? childSpan.style, + semanticsLabel: childSpan.semanticsLabel, + locale: childSpan.locale, + mouseCursor: childSpan.mouseCursor, + onEnter: childSpan.onEnter, + onExit: childSpan.onExit, + spellOut: childSpan.spellOut, ); } else { return WidgetSpan( + alignment: context.style!.verticalAlign + .toPlaceholderAlignment(context.style!.display), + baseline: TextBaseline.alphabetic, child: MultipleTapGestureDetector( onTap: onTap, child: GestureDetector( diff --git a/lib/src/builtins/styled_element_builtin.dart b/lib/src/builtins/styled_element_builtin.dart index 67e5b29e61..a56a8f278d 100644 --- a/lib/src/builtins/styled_element_builtin.dart +++ b/lib/src/builtins/styled_element_builtin.dart @@ -414,9 +414,17 @@ class StyledElementBuiltIn extends HtmlExtension { continue monospace; underline: case "u": - styledElement.style = Style( - textDecoration: TextDecoration.underline, - ); + for (var child in styledElement.children) { + if (child.attributes.containsKey("style")) { + final newStyle = inlineCssToStyle(child.attributes["style"], null); + if (newStyle != null) { + styledElement.style = styledElement.style + .merge(Style(textDecorationColor: newStyle.color)); + } + } + } + styledElement.style = styledElement.style + .merge(Style(textDecoration: TextDecoration.underline)); break; case "var": continue italics; @@ -427,27 +435,27 @@ class StyledElementBuiltIn extends HtmlExtension { @override InlineSpan build(ExtensionContext context) { - if (context.styledElement!.style.display == Display.listItem || - ((context.styledElement!.style.display == Display.block || - context.styledElement!.style.display == Display.inlineBlock) && + final style = context.styledElement!.style; + final display = style.display ?? Display.inline; + if (display.displayListItem || + ((display.isBlock || display == Display.inlineBlock) && (context.styledElement!.children.isNotEmpty || context.elementName == "hr"))) { return WidgetSpan( - alignment: PlaceholderAlignment.baseline, + alignment: style.verticalAlign.toPlaceholderAlignment(display), baseline: TextBaseline.alphabetic, child: CssBoxWidget.withInlineSpanChildren( key: AnchorKey.of(context.parser.key, context.styledElement), - style: context.styledElement!.style, + style: context.style!, shrinkWrap: context.parser.shrinkWrap, - childIsReplaced: ["iframe", "img", "video", "audio"] - .contains(context.styledElement!.name), + childIsReplaced: + ["iframe", "img", "video", "audio"].contains(context.elementName), children: context.builtChildrenMap!.entries .expandIndexed((i, child) => [ child.value, if (context.parser.shrinkWrap && i != context.styledElement!.children.length - 1 && - (child.key.style.display == Display.block || - child.key.style.display == Display.listItem) && + (child.key.style.display?.isBlock ?? false) && child.key.element?.localName != "html" && child.key.element?.localName != "body") const TextSpan(text: "\n", style: TextStyle(fontSize: 0)), @@ -463,7 +471,7 @@ class StyledElementBuiltIn extends HtmlExtension { .expandIndexed((index, child) => [ child.value, if (context.parser.shrinkWrap && - child.key.style.display == Display.block && + (child.key.style.display?.isBlock ?? false) && index != context.styledElement!.children.length - 1 && child.key.element?.parent?.localName != "th" && child.key.element?.parent?.localName != "td" && diff --git a/lib/src/css_box_widget.dart b/lib/src/css_box_widget.dart index 85679ea4b7..77d545d022 100644 --- a/lib/src/css_box_widget.dart +++ b/lib/src/css_box_widget.dart @@ -76,12 +76,12 @@ class CssBoxWidget extends StatelessWidget { border: style.border, color: style.backgroundColor, //Colors the padding and content boxes ), - width: _shouldExpandToFillBlock() ? double.infinity : null, padding: padding, child: top ? child : MediaQuery( - data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), + data: MediaQuery.of(context) + .copyWith(textScaler: TextScaler.linear(1.0)), child: child, ), ), @@ -155,16 +155,6 @@ class CssBoxWidget extends StatelessWidget { return null; } - /// Whether or not the content-box should expand its width to fill the - /// width available to it or if it should just let its inner content - /// determine the content-box's width. - bool _shouldExpandToFillBlock() { - return (style.display == Display.block || - style.display == Display.listItem) && - !childIsReplaced && - !shrinkWrap; - } - TextDirection _checkTextDirection( BuildContext context, TextDirection? direction) { final textDirection = direction ?? Directionality.maybeOf(context); @@ -179,8 +169,7 @@ class CssBoxWidget extends StatelessWidget { } class _CSSBoxRenderer extends MultiChildRenderObjectWidget { - _CSSBoxRenderer({ - Key? key, + const _CSSBoxRenderer({ required super.children, required this.display, required this.margins, @@ -192,7 +181,7 @@ class _CSSBoxRenderer extends MultiChildRenderObjectWidget { required this.childIsReplaced, required this.emValue, required this.shrinkWrap, - }) : super(key: key); + }); /// The Display type of the element final Display display; @@ -424,42 +413,62 @@ class RenderCSSBox extends RenderBox } } - static double getIntrinsicDimension(RenderBox? firstChild, - double Function(RenderBox child) mainChildSizeGetter) { + static double getIntrinsicDimension( + RenderBox? firstChild, + double Function(RenderBox child) mainChildSizeGetter, + double marginSpaceNeeded) { double extent = 0.0; RenderBox? child = firstChild; while (child != null) { final CSSBoxParentData childParentData = child.parentData! as CSSBoxParentData; - extent = math.max(extent, mainChildSizeGetter(child)); + try { + extent = math.max(extent, mainChildSizeGetter(child)); + } catch (_) { + // See https://github.com/flutter/flutter/issues/65895 + debugPrint( + "Due to Flutter layout restrictions (see https://github.com/flutter/flutter/issues/65895), contents set to `vertical-align: baseline` within an intrinsically-sized layout may not display as expected. If content is cut off or displaying incorrectly, please try setting vertical-align to 'bottom' on the problematic elements"); + } assert(child.parentData == childParentData); child = childParentData.nextSibling; } - return extent; + return extent + marginSpaceNeeded; } @override double computeMinIntrinsicWidth(double height) { return getIntrinsicDimension( - firstChild, (RenderBox child) => child.getMinIntrinsicWidth(height)); + firstChild, + (RenderBox child) => child.getMinIntrinsicWidth(height), + _calculateIntrinsicMargins().horizontal, + ); } @override double computeMaxIntrinsicWidth(double height) { return getIntrinsicDimension( - firstChild, (RenderBox child) => child.getMaxIntrinsicWidth(height)); + firstChild, + (RenderBox child) => child.getMaxIntrinsicWidth(height), + _calculateIntrinsicMargins().horizontal, + ); } @override double computeMinIntrinsicHeight(double width) { return getIntrinsicDimension( - firstChild, (RenderBox child) => child.getMinIntrinsicHeight(width)); + firstChild, + (RenderBox child) => child.getMinIntrinsicHeight(width), + _calculateIntrinsicMargins().vertical, + ); } @override double computeMaxIntrinsicHeight(double width) { return getIntrinsicDimension( - firstChild, (RenderBox child) => child.getMaxIntrinsicHeight(width)); + firstChild, + (RenderBox child) => child.getMaxIntrinsicHeight(width), + _calculateIntrinsicMargins().vertical, + ); } @override @@ -475,6 +484,12 @@ class RenderCSSBox extends RenderBox ).parentSize; } + @override + double? computeDryBaseline( + covariant BoxConstraints constraints, TextBaseline baseline) { + return null; + } + _Sizes _computeSize( {required BoxConstraints constraints, required ChildLayouter layoutChild}) { @@ -493,7 +508,7 @@ class RenderCSSBox extends RenderBox RenderBox? markerBoxChild = parentData.nextSibling; // Calculate child size - final childConstraints = constraints.copyWith( + BoxConstraints childConstraints = constraints.copyWith( maxWidth: (this.width.unit != Unit.auto) ? this.width.value : containingBlockSize.width - @@ -507,11 +522,31 @@ class RenderCSSBox extends RenderBox minWidth: (this.width.unit != Unit.auto) ? this.width.value : 0, minHeight: (this.height.unit != Unit.auto) ? this.height.value : 0, ); - final Size childSize = layoutChild(child, childConstraints); + if (markerBoxChild != null) { layoutChild(markerBoxChild, childConstraints); } + // If this element is a block element and not otherwise constrained, + // we constrain the child Container to fill the entire width of this + // Widget's parent, if possible. This is equivalent to setting + // `width: double.infinity` on the inner Container, but we do it here + // to keep the infinite width from being applied if the parent's width is + // also infinite. + if (display.isBlock && + !shrinkWrap && + !childIsReplaced && + containingBlockSize.width.isFinite) { + childConstraints = childConstraints.enforce(BoxConstraints( + maxWidth: math.max( + containingBlockSize.width, + childConstraints.maxWidth, + ), + minWidth: childConstraints.maxWidth, + )); + } + final Size childSize = layoutChild(child, childConstraints); + // Calculate used values of margins based on rules final usedMargins = _calculateUsedMargins(childSize, containingBlockSize); final horizontalMargins = @@ -521,31 +556,22 @@ class RenderCSSBox extends RenderBox //Calculate Width and Height of CSS Box height = childSize.height; - switch (display) { - case Display.block: - width = (shrinkWrap || childIsReplaced) - ? childSize.width + horizontalMargins - : containingBlockSize.width; - height = childSize.height + verticalMargins; - break; - case Display.inline: - width = childSize.width + horizontalMargins; - height = childSize.height; - break; - case Display.inlineBlock: - width = childSize.width + horizontalMargins; - height = childSize.height + verticalMargins; - break; - case Display.listItem: - width = shrinkWrap - ? childSize.width + horizontalMargins - : containingBlockSize.width; - height = childSize.height + verticalMargins; - break; - case Display.none: - width = 0; - height = 0; - break; + if (display.displayBox == DisplayBox.none) { + width = 0; + height = 0; + } else if (display == Display.inlineBlock) { + width = childSize.width + horizontalMargins; + height = childSize.height + verticalMargins; + } else if (display.isBlock) { + width = (shrinkWrap || + childIsReplaced || + containingBlockSize.width.isInfinite) + ? childSize.width + horizontalMargins + : containingBlockSize.width; + height = childSize.height + verticalMargins; + } else { + width = childSize.width + horizontalMargins; + height = childSize.height; } return _Sizes(constraints.constrain(Size(width, height)), childSize); @@ -575,26 +601,14 @@ class RenderCSSBox extends RenderBox double leftOffset = 0; double topOffset = 0; - switch (display) { - case Display.block: - leftOffset = leftMargin; - topOffset = topMargin; - break; - case Display.inline: - leftOffset = leftMargin; - break; - case Display.inlineBlock: - leftOffset = leftMargin; - topOffset = topMargin; - break; - case Display.listItem: - leftOffset = leftMargin; - topOffset = topMargin; - break; - case Display.none: - //No offset - break; + + if (display.isBlock || display == Display.inlineBlock) { + leftOffset = leftMargin; + topOffset = topMargin; + } else if (display.displayOutside == DisplayOutside.inline) { + leftOffset = leftMargin; } + childParentData.offset = Offset(leftOffset, topOffset); assert(child.parentData == childParentData); @@ -602,13 +616,15 @@ class RenderCSSBox extends RenderBox RenderBox? markerBox = childParentData.nextSibling; if (markerBox != null) { final markerBoxParentData = markerBox.parentData! as CSSBoxParentData; - final distance = (child.getDistanceToBaseline(TextBaseline.alphabetic, - onlyReal: true) ?? - 0) + - topOffset; - final offsetHeight = distance - - (markerBox.getDistanceToBaseline(TextBaseline.alphabetic) ?? - markerBox.size.height); + // final distance = (child.getDistanceToBaseline(TextBaseline.alphabetic, + // onlyReal: true) ?? + // 0) + + // topOffset; + // final offsetHeight = distance - + // (markerBox.getDistanceToBaseline(TextBaseline.alphabetic) ?? + // markerBox.size.height); + // TODO handle block children better by modifying above approach + final offsetHeight = topOffset; switch (_textDirection) { case TextDirection.rtl: markerBoxParentData.offset = Offset( @@ -628,7 +644,7 @@ class RenderCSSBox extends RenderBox Margins _calculateUsedMargins(Size childSize, Size containingBlockSize) { //We assume that margins have already been preprocessed - // (i.e. they are non-null and either px units or auto. + // (i.e. they are non-null and either px units or auto). assert(margins.left != null && margins.right != null); assert(margins.left!.unit == Unit.px || margins.left!.unit == Unit.auto); assert(margins.right!.unit == Unit.px || margins.right!.unit == Unit.auto); @@ -737,6 +753,40 @@ class RenderCSSBox extends RenderBox ); } + Margins _calculateIntrinsicMargins() { + //We assume that margins have already been preprocessed + // (i.e. they are non-null and either px units or auto). + assert(margins.left != null && margins.right != null); + assert(margins.left!.unit == Unit.px || margins.left!.unit == Unit.auto); + assert(margins.right!.unit == Unit.px || margins.right!.unit == Unit.auto); + + Margin marginLeft = margins.left!; + Margin marginRight = margins.right!; + + bool marginLeftIsAuto = marginLeft.unit == Unit.auto; + bool marginRightIsAuto = marginRight.unit == Unit.auto; + + if (display.isBlock) { + if (marginLeftIsAuto) { + marginLeft = Margin(0); + } + + if (marginRightIsAuto) { + marginRight = Margin(0); + } + } else { + marginLeft = Margin(0); + marginRight = Margin(0); + } + + return Margins( + left: marginLeft, + right: marginRight, + top: margins.top, + bottom: margins.bottom, + ); + } + @override bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { return defaultHitTestChildren(result, position: position); @@ -769,7 +819,9 @@ extension Normalize on Dimension { double _calculateEmValue(Style style, BuildContext buildContext) { return (style.fontSize?.emValue ?? 16) * - MediaQuery.textScaleFactorOf(buildContext) * + (MediaQuery.maybeTextScalerOf(buildContext) + ?.scale(style.fontSize?.emValue ?? 16) ?? + 1.0) * MediaQuery.of(buildContext).devicePixelRatio; } diff --git a/lib/src/css_parser.dart b/lib/src/css_parser.dart index c0fcf02f8b..02cbe8a3ae 100644 --- a/lib/src/css_parser.dart +++ b/lib/src/css_parser.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:collection/collection.dart'; import 'package:csslib/visitor.dart' as css; import 'package:csslib/parser.dart' as cssparser; @@ -294,7 +292,7 @@ Style declarationsToStyle(Map> declarations) { style.border = newBorder; break; case 'color': - style.color = + style.color = style.textDecorationColor = ExpressionMapping.expressionToColor(value.first) ?? style.color; break; case 'direction': diff --git a/lib/src/processing/margins.dart b/lib/src/processing/margins.dart index fb1378912c..2598fcb096 100644 --- a/lib/src/processing/margins.dart +++ b/lib/src/processing/margins.dart @@ -32,8 +32,10 @@ class MarginProcessing { //Collapsing should be depth-first. tree.children.forEach(_collapseMargins); - //The root boxes do not collapse. - if (tree.name == '[Tree Root]' || tree.name == 'html') { + //The root boxes and table/ruby elements do not collapse. + if (tree.name == '[Tree Root]' || + tree.name == 'html' || + tree.style.display?.displayInternal != null) { return tree; } @@ -67,7 +69,8 @@ class MarginProcessing { // Handle case (3) from above. // Bottom margins cannot collapse if the element has padding - if ((tree.style.padding?.bottom ?? tree.style.padding?.blockEnd ?? 0) == + if ((tree.style.padding?.bottom?.value ?? + tree.style.padding?.blockEnd?.value) == 0) { final parentBottom = tree.style.margin?.bottom?.value ?? tree.style.margin?.blockEnd?.value ?? diff --git a/lib/src/processing/whitespace.dart b/lib/src/processing/whitespace.dart index 6dc2e74a3f..bf5cf40277 100644 --- a/lib/src/processing/whitespace.dart +++ b/lib/src/processing/whitespace.dart @@ -226,8 +226,7 @@ class WhitespaceProcessing { /// (4) Replace any instances of two or more spaces with a single space. static String _removeUnnecessaryWhitespace(String text) { return text - .replaceAll(RegExp(r" *(?=\n)"), "") - .replaceAll(RegExp(r"(?<=\n) *"), "") + .replaceAll(RegExp(r" *\n *"), "\n") .replaceAll("\n", " ") .replaceAll("\t", " ") .replaceAll(RegExp(r" {2,}"), " "); diff --git a/lib/src/style.dart b/lib/src/style.dart index 004b32f4e7..014f7ff91f 100644 --- a/lib/src/style.dart +++ b/lib/src/style.dart @@ -1,10 +1,9 @@ -import 'dart:ui'; - import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/src/css_parser.dart'; //Export Style value-unit APIs +export 'package:flutter_html/src/style/display.dart'; export 'package:flutter_html/src/style/margin.dart'; export 'package:flutter_html/src/style/padding.dart'; export 'package:flutter_html/src/style/length.dart'; @@ -49,7 +48,7 @@ class Style { /// CSS attribute "`display`" /// /// Inherited: no, - /// Default: unspecified, + /// Default: inline, Display? display; /// CSS attribute "`font-family`" @@ -229,6 +228,12 @@ class Style { /// TextOverflow? textOverflow; + /// CSS Attribute "`text-transform`" + /// + /// `uppercase`, `lowercase`, `capitalize`, and `none` supported. + /// + /// Inherited: yes, + /// Default: `none` TextTransform? textTransform; Style({ @@ -269,10 +274,9 @@ class Style { this.alignment, this.maxLines, this.textOverflow, - this.textTransform = TextTransform.none, + this.textTransform, }) { - if (alignment == null && - (display == Display.block || display == Display.listItem)) { + if (alignment == null && (display?.isBlock ?? false)) { alignment = Alignment.centerLeft; } } @@ -299,7 +303,7 @@ class Style { TextStyle generateTextStyle() { return TextStyle( - backgroundColor: backgroundColor, + backgroundColor: (display?.isBlock ?? false) ? null : backgroundColor, color: color, decoration: textDecoration, decorationColor: textDecorationColor, @@ -400,6 +404,10 @@ class Style { child.textDecoration ?? TextDecoration.none, textDecoration ?? TextDecoration.none, ]), + textDecorationColor: child.textDecorationColor ?? textDecorationColor, + textDecorationThickness: + child.textDecorationThickness ?? textDecorationThickness, + textDecorationStyle: child.textDecorationStyle ?? textDecorationStyle, textShadow: child.textShadow ?? textShadow, whiteSpace: child.whiteSpace ?? whiteSpace, wordSpacing: child.wordSpacing ?? wordSpacing, @@ -591,14 +599,6 @@ extension MergeBorders on Border? { } } -enum Display { - block, - inline, - inlineBlock, - listItem, - none, -} - enum ListStyleType { arabicIndic('arabic-indic'), armenian('armenian'), @@ -664,7 +664,7 @@ enum ListStyleType { factory ListStyleType.fromName(String name) { return ListStyleType.values.firstWhere((value) { return name == value.counterStyle; - }); + }, orElse: () => ListStyleType.disc); } } @@ -692,7 +692,34 @@ enum VerticalAlign { sup, top, bottom, - middle, + middle; + + /// Converts this [VerticalAlign] to a [PlaceholderAlignment] given the + /// [Display] type of the current context + PlaceholderAlignment toPlaceholderAlignment(Display? display) { + // vertical-align only applies to inline context elements. + // If we aren't in such a context, use the default 'bottom' alignment. + // Also note that the default display, if it is not set, is inline, so we + // treat null `display` values as if they were inline by default. + if (display != Display.inline && + display != Display.inlineBlock && + display != null) { + return PlaceholderAlignment.bottom; + } + + switch (this) { + case VerticalAlign.baseline: + case VerticalAlign.sub: + case VerticalAlign.sup: + return PlaceholderAlignment.baseline; + case VerticalAlign.top: + return PlaceholderAlignment.top; + case VerticalAlign.bottom: + return PlaceholderAlignment.bottom; + case VerticalAlign.middle: + return PlaceholderAlignment.middle; + } + } } enum WhiteSpace { diff --git a/lib/src/style/display.dart b/lib/src/style/display.dart new file mode 100644 index 0000000000..b69c2100c9 --- /dev/null +++ b/lib/src/style/display.dart @@ -0,0 +1,239 @@ +/// Equivalent to CSS `display` +/// +/// (https://www.w3.org/TR/css-display-3/#the-display-properties) +enum Display { + /// Equivalent to css `display: none;` + none( + displayBox: DisplayBox.none, + ), + + /// Equivalent to css `display: contents;` + /// + /// Not supported by flutter_html + contents( + displayBox: DisplayBox.contents, + ), + + /// Equivalent to css `display: block;` + block( + displayOutside: DisplayOutside.block, + displayInside: DisplayInside.flow, + ), + + /// Equivalent to css `display: flow-root;` + /// + /// Not supported by flutter_html + flowRoot( + displayOutside: DisplayOutside.block, + displayInside: DisplayInside.flowRoot, + ), + + /// Equivalent to css `display: inline;` + inline( + displayOutside: DisplayOutside.inline, + displayInside: DisplayInside.flow, + ), + + /// Equivalent to css `display: inline-block;` + inlineBlock( + displayOutside: DisplayOutside.inline, + displayInside: DisplayInside.flowRoot, + ), + + /// Equivalent to css `display: run-in;` + /// + /// Not supported by flutter_html + runIn( + displayOutside: DisplayOutside.runIn, + displayInside: DisplayInside.flow, + ), + + /// Equivalent to css `display: list-item;` + listItem( + displayOutside: DisplayOutside.block, + displayInside: DisplayInside.flow, + displayListItem: true, + ), + + /// Equivalent to css `display: inline list-item;` + inlineListItem( + displayOutside: DisplayOutside.inline, + displayInside: DisplayInside.flow, + displayListItem: true, + ), + + /// Equivalent to css `display: flex;` + /// + /// Not supported by flutter_html + flex( + displayOutside: DisplayOutside.block, + displayInside: DisplayInside.flex, + ), + + /// Equivalent to css `display: inline-flex;` + /// + /// Not supported by flutter_html + inlineFlex( + displayOutside: DisplayOutside.inline, + displayInside: DisplayInside.flex, + ), + + /// Equivalent to css `display: grid;` + /// + /// Not supported by flutter_html + grid( + displayOutside: DisplayOutside.block, + displayInside: DisplayInside.grid, + ), + + /// Equivalent to css `display: inline-grid;` + /// + /// Not supported by flutter_html + inlineGrid( + displayOutside: DisplayOutside.inline, + displayInside: DisplayInside.grid, + ), + + /// Equivalent to css `display: ruby;` + ruby( + displayOutside: DisplayOutside.inline, + displayInside: DisplayInside.ruby, + ), + + /// Equivalent to css `display: block ruby;` + blockRuby( + displayOutside: DisplayOutside.block, + displayInside: DisplayInside.ruby, + ), + + /// Equivalent to css `display: table;` + table( + displayOutside: DisplayOutside.block, + displayInside: DisplayInside.table, + ), + + /// Equivalent to css `display: inline-table;` + inlineTable( + displayOutside: DisplayOutside.inline, + displayInside: DisplayInside.table, + ), + + /// Equivalent to css `display: table-row-group;` + tableRowGroup( + displayInternal: DisplayInternal.tableRowGroup, + ), + + /// Equivalent to css `display: table-header-group;` + tableHeaderGroup( + displayInternal: DisplayInternal.tableHeaderGroup, + ), + + /// Equivalent to css `display: table-footer-group;` + tableFooterGroup( + displayInternal: DisplayInternal.tableFooterGroup, + ), + + /// Equivalent to css `display: table-row;` + tableRow( + displayInternal: DisplayInternal.tableRowGroup, + ), + + /// Equivalent to css `display: table-cell;` + tableCell( + displayInternal: DisplayInternal.tableCell, + displayInside: DisplayInside.flowRoot, + ), + + /// Equivalent to css `display: table-column-group;` + tableColumnGroup( + displayInternal: DisplayInternal.tableColumnGroup, + ), + + /// Equivalent to css `display: table-column;` + tableColumn( + displayInternal: DisplayInternal.tableColumn, + ), + + /// Equivalent to css `display: table-caption;` + tableCaption( + displayInternal: DisplayInternal.tableCaption, + displayInside: DisplayInside.flowRoot, + ), + + /// Equivalent to css `display: ruby-base;` + rubyBase( + displayInternal: DisplayInternal.rubyBase, + displayInside: DisplayInside.flow, + ), + + /// Equivalent to css `display: ruby-text;` + rubyText( + displayInternal: DisplayInternal.rubyText, + displayInside: DisplayInside.flow, + ), + + /// Equivalent to css `display: ruby-base-container;` + rubyBaseContainer( + displayInternal: DisplayInternal.rubyBaseContainer, + ), + + /// Equivalent to css `display: ruby-text-container;` + rubyTextContainer( + displayInternal: DisplayInternal.rubyTextContainer, + ); + + const Display({ + this.displayOutside, + this.displayInside, + this.displayListItem = false, + this.displayInternal, + this.displayBox, + }); + + final DisplayOutside? displayOutside; + final DisplayInside? displayInside; + final bool displayListItem; + final DisplayInternal? displayInternal; + final DisplayBox? displayBox; + + /// Evaluates to `true` if `displayOutside` is equal to + /// `DisplayOutside.block`. + bool get isBlock { + return displayOutside == DisplayOutside.block; + } +} + +enum DisplayOutside { + block, + inline, + runIn, // not supported +} + +enum DisplayInside { + flow, + flowRoot, + table, + flex, // not supported + grid, // not supported + ruby, +} + +enum DisplayInternal { + tableRowGroup, + tableHeaderGroup, + tableFooterGroup, + tableRow, + tableCell, + tableColumnGroup, + tableColumn, + tableCaption, + rubyBase, + rubyText, + rubyBaseContainer, + rubyTextContainer, +} + +enum DisplayBox { + contents, // not supported + none, +} diff --git a/lib/src/style/fontsize.dart b/lib/src/style/fontsize.dart index 793124fe14..fd6bb79652 100644 --- a/lib/src/style/fontsize.dart +++ b/lib/src/style/fontsize.dart @@ -1,7 +1,7 @@ import 'length.dart'; class FontSize extends LengthOrPercent { - FontSize(double size, [Unit unit = Unit.px]) : super(size, unit); + FontSize(super.size, [super.unit]); // These values are calculated based off of the default (`medium`) // being 14px. diff --git a/lib/src/style/margin.dart b/lib/src/style/margin.dart index d2cbe157d1..257fa0b296 100644 --- a/lib/src/style/margin.dart +++ b/lib/src/style/margin.dart @@ -63,6 +63,19 @@ class Margins { blockStart?.unit == Unit.auto ? blockStart : Margin(0, Unit.px)); } + /// The total margin in the horizontal direction. + double get horizontal => + (left?.value ?? inlineStart?.value ?? 0) + + (right?.value ?? inlineEnd?.value ?? 0); + + /// The total margin in the vertical direction. + double get vertical => + (top?.value ?? blockStart?.value ?? 0) + + (bottom?.value ?? blockEnd?.value ?? 0); + + /// The size that this [Margins] would occupy with an empty interior. + Size get collapsedSize => Size(horizontal, vertical); + Margins copyWith({ Margin? left, Margin? right, diff --git a/lib/src/tree/replaced_element.dart b/lib/src/tree/replaced_element.dart index f2ae859ca0..69f550ba06 100644 --- a/lib/src/tree/replaced_element.dart +++ b/lib/src/tree/replaced_element.dart @@ -34,11 +34,11 @@ class TextContentElement extends ReplacedElement { String? text; TextContentElement({ - required Style style, + required super.style, required this.text, required super.node, dom.Element? element, - }) : super(name: "[text]", style: style, elementId: "[[No ID]]"); + }) : super(name: "[text]", elementId: "[[No ID]]"); @override String toString() { @@ -54,8 +54,8 @@ class LinebreakContentElement extends ReplacedElement { } class EmptyContentElement extends ReplacedElement { - EmptyContentElement({required super.node, String name = "empty"}) - : super(name: name, style: Style(), elementId: "[[No ID]]"); + EmptyContentElement({required super.node, super.name = "empty"}) + : super(style: Style(), elementId: "[[No ID]]"); } class RubyElement extends ReplacedElement { @@ -64,13 +64,11 @@ class RubyElement extends ReplacedElement { RubyElement({ required this.element, - required List children, - String name = "ruby", + required List super.children, + super.name = "ruby", required super.node, }) : super( - name: name, alignment: PlaceholderAlignment.middle, style: Style(), - elementId: element.id, - children: children); + elementId: element.id); } diff --git a/lib/src/tree/styled_element.dart b/lib/src/tree/styled_element.dart index bf2b30f5dc..f91ebf7c8e 100644 --- a/lib/src/tree/styled_element.dart +++ b/lib/src/tree/styled_element.dart @@ -4,7 +4,7 @@ import 'package:flutter_html/src/style.dart'; import 'package:html/dom.dart' as dom; //TODO(Sub6Resources): don't use the internal code of the html package as it may change unexpectedly. //ignore: implementation_imports -import 'package:html/src/query_selector.dart'; +import 'package:html/src/query_selector.dart' as qs; import 'package:list_counter/list_counter.dart'; /// A [StyledElement] applies a style to all of its children. @@ -26,6 +26,14 @@ class StyledElement { required this.node, }); + bool matches(dom.Element element, String selector) { + try { + return qs.matches(element, selector); + } catch (_) { + return false; + } + } + bool matchesSelector(String selector) { return (element != null && matches(element!, selector)) || name == selector; } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 2bd9d80d8c..e21ff8a636 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -32,10 +32,10 @@ class MultipleTapGestureDetector extends InheritedWidget { final void Function()? onTap; const MultipleTapGestureDetector({ - Key? key, - required Widget child, + super.key, + required super.child, required this.onTap, - }) : super(key: key, child: child); + }); static MultipleTapGestureDetector? of(BuildContext context) { return context diff --git a/packages/flutter_html_all/CHANGELOG.md b/packages/flutter_html_all/CHANGELOG.md index 21728dfb58..933565d4ec 100644 --- a/packages/flutter_html_all/CHANGELOG.md +++ b/packages/flutter_html_all/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.0 + + - Graduate package to a stable release. See pre-releases prior to this version for changelog entries. + ## 3.0.0-beta.2 - Update a dependency to the latest release. diff --git a/packages/flutter_html_all/LICENSE b/packages/flutter_html_all/LICENSE index 89971b33a6..230b35b47f 100644 --- a/packages/flutter_html_all/LICENSE +++ b/packages/flutter_html_all/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2022 The flutter_html developers +Copyright (c) 2019-2025 The flutter_html developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/flutter_html_all/lib/flutter_html_all.dart b/packages/flutter_html_all/lib/flutter_html_all.dart index 2b34957522..75834d1bc8 100644 --- a/packages/flutter_html_all/lib/flutter_html_all.dart +++ b/packages/flutter_html_all/lib/flutter_html_all.dart @@ -1,6 +1,6 @@ /// Package flutter_html_all is used to get access to all /// of the extended features of the flutter_html package. -library flutter_html_all; +library; export 'package:flutter_html_audio/flutter_html_audio.dart'; export 'package:flutter_html_iframe/flutter_html_iframe.dart'; diff --git a/packages/flutter_html_all/pubspec.yaml b/packages/flutter_html_all/pubspec.yaml index f298142146..7baaa65ae0 100644 --- a/packages/flutter_html_all/pubspec.yaml +++ b/packages/flutter_html_all/pubspec.yaml @@ -1,35 +1,33 @@ name: flutter_html_all description: All optional flutter_html widgets, bundled into a single package. -version: 3.0.0-beta.2 +version: 3.0.0 homepage: https://github.com/Sub6Resources/flutter_html environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.2.0" + sdk: ">=3.2.0 <4.0.0" + flutter: ">=3.0.0" dependencies: flutter: sdk: flutter - html: '>=0.15.0 <1.0.0' - flutter_html: ^3.0.0-beta.2 - flutter_html_audio: ^3.0.0-beta.2 - flutter_html_iframe: ^3.0.0-beta.2 - flutter_html_math: ^3.0.0-beta.2 - flutter_html_svg: ^3.0.0-beta.2 - flutter_html_table: ^3.0.0-beta.2 - flutter_html_video: ^3.0.0-beta.2 + flutter_html: ^3.0.0 + flutter_html_audio: ^3.0.0 + flutter_html_iframe: ^3.0.0 + flutter_html_math: ^3.0.0 + flutter_html_svg: ^3.0.0 + flutter_html_table: ^3.0.0 + flutter_html_video: ^3.0.0 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.1 + flutter_lints: ^5.0.0 funding: - - https://opencollective.com/flutter_html + - https://github.com/sponsors/Sub6Resources topics: - html - css - widgets - layout - - flutter_html diff --git a/packages/flutter_html_audio/CHANGELOG.md b/packages/flutter_html_audio/CHANGELOG.md index b42b09c260..3b69fe85a8 100644 --- a/packages/flutter_html_audio/CHANGELOG.md +++ b/packages/flutter_html_audio/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.0 + + - Graduate package to a stable release. See pre-releases prior to this version for changelog entries. + ## 3.0.0-beta.2 - **FIX**: improve API for ExtensionContext and export marker.dart ([#1273](https://github.com/sub6resources/flutter_html/issues/1273)). ([27e33a95](https://github.com/sub6resources/flutter_html/commit/27e33a955e872d47306db9480f74f6da2e9a028a)) diff --git a/packages/flutter_html_audio/LICENSE b/packages/flutter_html_audio/LICENSE index 89971b33a6..230b35b47f 100644 --- a/packages/flutter_html_audio/LICENSE +++ b/packages/flutter_html_audio/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2022 The flutter_html developers +Copyright (c) 2019-2025 The flutter_html developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/flutter_html_audio/README.md b/packages/flutter_html_audio/README.md index 81ac741006..16f4f6eeaa 100644 --- a/packages/flutter_html_audio/README.md +++ b/packages/flutter_html_audio/README.md @@ -6,12 +6,12 @@ This package renders audio elements using the [`chewie_audio`](https://pub.dev/p The package considers the attributes `controls`, `loop`, `src`, `autoplay`, `width`, and `muted` when rendering the audio widget. -#### Registering the `CustomRender`: +#### Registering the `AudioHtmlExtension`: ```dart Widget html = Html( - customRenders: { - audioMatcher(): audioRender(), + extensions: { + AudioHtmlExtension(), } ); -``` \ No newline at end of file +``` diff --git a/packages/flutter_html_audio/lib/flutter_html_audio.dart b/packages/flutter_html_audio/lib/flutter_html_audio.dart index ecd781d337..ec15cf9182 100644 --- a/packages/flutter_html_audio/lib/flutter_html_audio.dart +++ b/packages/flutter_html_audio/lib/flutter_html_audio.dart @@ -1,4 +1,4 @@ -library flutter_html_audio; +library; import 'package:chewie_audio/chewie_audio.dart'; import 'package:flutter/material.dart'; @@ -37,10 +37,10 @@ class AudioWidget extends StatefulWidget { final AudioControllerCallback? callback; const AudioWidget({ - Key? key, + super.key, required this.context, this.callback, - }) : super(key: key); + }); @override State createState() => _AudioWidgetState(); @@ -60,8 +60,8 @@ class _AudioWidgetState extends State { ]; if (sources.isNotEmpty && sources.first != null) { - audioController = VideoPlayerController.network( - sources.first ?? "", + audioController = VideoPlayerController.networkUrl( + Uri.tryParse(sources.first ?? "") ?? Uri(), ); chewieAudioController = ChewieAudioController( videoPlayerController: audioController!, diff --git a/packages/flutter_html_audio/pubspec.yaml b/packages/flutter_html_audio/pubspec.yaml index 36c354b637..18723caebc 100644 --- a/packages/flutter_html_audio/pubspec.yaml +++ b/packages/flutter_html_audio/pubspec.yaml @@ -1,31 +1,30 @@ name: flutter_html_audio description: This extension package allows the