├── example ├── README.md ├── analysis_options.yaml ├── .gitignore ├── pubspec.yaml ├── lib │ └── main.dart └── LICENSE ├── CHANGELOG.md ├── doc └── assets │ └── screenshot.jpg ├── test └── src │ └── liquido_test.dart ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yaml └── workflows │ └── main.yaml ├── lib ├── liquido.dart └── src │ ├── glass_options.dart │ ├── glass.dart │ └── glass_render_object_widget.dart ├── pubspec.yaml ├── .gitignore ├── coverage_badge.svg ├── .vscode └── launch.json ├── README.md ├── CONTRIBUTING.md ├── shaders └── liquido_impeller.frag ├── analysis_options.yaml └── LICENSE /example/README.md: -------------------------------------------------------------------------------- 1 | # Liquido Flutter Example App 2 | 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # v0.0.0+1 3 | - Liteally nothing 4 | -------------------------------------------------------------------------------- /doc/assets/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renancaraujo/liquido/HEAD/doc/assets/screenshot.jpg -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: ../analysis_options.yaml 2 | 3 | linter: 4 | rules: 5 | public_member_api_docs: false -------------------------------------------------------------------------------- /test/src/liquido_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | group('Liquido', () { 5 | // LOL 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | 5 | ## (Required) Link to the issue 6 | 7 | Link to the issue related to this contribution # 8 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Flutter/Dart/Pub related 3 | /build/ 4 | 5 | 6 | # Platform directories 7 | ios/ 8 | android/ 9 | macos/ 10 | windows/ 11 | linux/ 12 | web/ 13 | .metadata 14 | 15 | # Forgive me lord for I have sinned 16 | test/ 17 | 18 | -------------------------------------------------------------------------------- /lib/liquido.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025, Renan Araujo 2 | // Use of this source code is governed by a MPL-2.0 license that can be 3 | // found in the LICENSE file. 4 | 5 | /// Liquido is a way to implement the Liquid Glass effect on 6 | /// Flutter-rendered surfaces 7 | library; 8 | 9 | export 'src/glass.dart'; 10 | export 'src/glass_options.dart'; 11 | export 'src/glass_render_object_widget.dart'; 12 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: monthly 7 | groups: 8 | github-actions: 9 | patterns: 10 | - "*" 11 | - package-ecosystem: pub 12 | directory: / 13 | schedule: 14 | interval: monthly 15 | groups: 16 | root-pub: 17 | patterns: 18 | - "*" 19 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: "Liquido Examples App - Showcasing usage example on flutter" 3 | publish_to: "none" 4 | 5 | version: 0.0.0+0 6 | 7 | environment: 8 | sdk: ^3.8.1 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | google_fonts: ^6.1.0 14 | liquido: 15 | path: ../ 16 | mesh: ^0.5.0 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | 21 | flutter: 22 | uses-material-design: true 23 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: liquido 2 | description: Liquido is a way to implement the Liquid Glass effect on Flutter-rendered surfaces 3 | version: 0.1.0+1 4 | 5 | 6 | environment: 7 | sdk: ^3.7.0 8 | flutter: ">=3.27.0" 9 | 10 | dependencies: 11 | equatable: ^2.0.7 12 | flutter: 13 | sdk: flutter 14 | flutter_shaders: ^0.1.3 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | flutter: 21 | shaders: 22 | - shaders/liquido_impeller.frag 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # VSCode related 20 | .vscode/* 21 | 22 | !**/.vscode/launch.json 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | pubspec.lock 35 | 36 | # Web related 37 | lib/generated_plugin_registrant.dart 38 | 39 | # Symbolication related 40 | app.*.symbols 41 | 42 | # Obfuscation related 43 | app.*.map.json 44 | 45 | # Test related 46 | coverage -------------------------------------------------------------------------------- /coverage_badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | coverage 16 | coverage 17 | 100% 18 | 100% 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | pull_request: 9 | types: [ opened, synchronize, reopened, ready_for_review, converted_to_draft ] 10 | branches: 11 | - "*" 12 | paths: 13 | - ".github/workflows/main.yaml" 14 | - "./lib/**" 15 | - "./test/**" 16 | - "./pubspec.yaml" 17 | 18 | jobs: 19 | semantic_pull_request: 20 | defaults: 21 | run: 22 | working-directory: "." 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Semantic pull request 26 | if: github.event_name == 'pull_request' 27 | uses: amannn/action-semantic-pull-request@v5.5.3 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | with: 31 | validateSingleCommit: false 32 | ignoreLabels: | 33 | bot 34 | ignore-semantic-pull-request 35 | wip: false 36 | build: 37 | defaults: 38 | run: 39 | working-directory: "." 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: checkout 43 | uses: actions/checkout@v4 44 | with: 45 | fetch-depth: 2 46 | - name: setup flutter 47 | uses: subosito/flutter-action@v2 48 | with: 49 | channel: stable 50 | - name: analyze 51 | run: flutter analyze lib test 52 | - name: test 53 | run: flutter test 54 | - name: format 55 | run: dart format --set-exit-if-changed . 56 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "example", 9 | "cwd": "example", 10 | "request": "launch", 11 | "type": "dart" 12 | }, 13 | { 14 | "name": "example (profile mode)", 15 | "cwd": "example", 16 | "request": "launch", 17 | "type": "dart", 18 | "flutterMode": "profile" 19 | }, 20 | { 21 | "name": "example (release mode)", 22 | "cwd": "example", 23 | "request": "launch", 24 | "type": "dart", 25 | "flutterMode": "release" 26 | }, 27 | { 28 | "name": "example (no impeller)", 29 | "cwd": "example", 30 | "request": "launch", 31 | "type": "dart", 32 | "args": ["--no-enable-impeller"] 33 | }, 34 | { 35 | "name": "example (no impeller, profile mode)", 36 | "cwd": "example", 37 | "request": "launch", 38 | "type": "dart", 39 | "args": ["--no-enable-impeller"], 40 | "flutterMode": "profile" 41 | }, 42 | { 43 | "name": "example (no impeller, release mode)", 44 | "cwd": "example", 45 | "request": "launch", 46 | "type": "dart", 47 | "args": ["--no-enable-impeller"], 48 | "flutterMode": "release" 49 | }, 50 | { 51 | "name": "example (enable impeller)", 52 | "cwd": "example", 53 | "request": "launch", 54 | "type": "dart", 55 | "args": ["--enable-impeller"] 56 | }, 57 | { 58 | "name": "example (enable impeller, profile mode)", 59 | "cwd": "example", 60 | "request": "launch", 61 | "type": "dart", 62 | "args": ["--enable-impeller"], 63 | "flutterMode": "profile" 64 | }, 65 | { 66 | "name": "example (enable impeller, release mode)", 67 | "cwd": "example", 68 | "request": "launch", 69 | "type": "dart", 70 | "args": ["--enable-impeller"], 71 | "flutterMode": "release" 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Liquido 🌊 2 | 3 | [![License: MPL 2.0][license_badge]][license_link] 4 | 5 | > **⚠️ EXPERIMENTAL - TECHNOLOGY DEMONSTRATOR ONLY** 6 | > This package is highly experimental and not intended for production use. It was not created with performance in mind and should be considered a technology demonstration only. Use at your own risk. 7 | > 8 | > **⚠️ IMPELLER ONLY** 9 | > This package is only compatible with Flutter's Impeller rendering engine. It will not work with the Skia backend. 10 | 11 | Create beautiful liquid glass effects in your Flutter applications. 12 | 13 | 14 | 15 | ## The Story Behind Liquido 16 | 17 | This package started as a creative experiment after Apple introduced their liquid glass effects in their UI. I had some ideas about how to implement similar visuals in Flutter, and Liquido is the result of a few hours of experimentation. The goal was to see how far I could push Flutter's rendering capabilities to create realistic glass effects that respond to the content behind them. 18 | 19 | I'll be writing a more detailed blog post about the technical challenges and solutions soon - stay tuned! 20 | 21 | ## Getting Started 🚀 22 | 23 | Since this is an experimental package, you'll need to add it directly from GitHub to your Flutter project: 24 | 25 | ```yaml 26 | dependencies: 27 | liquido: 28 | git: 29 | url: https://github.com/renancaraujo/liquido.git 30 | ref: main 31 | ``` 32 | 33 | Then import it in your code: 34 | 35 | ```dart 36 | import 'package:liquido/liquido.dart'; 37 | ``` 38 | 39 | ### Impeller Requirement 40 | 41 | This package relies on shader features that are only available in Flutter's Impeller rendering engine. To ensure compatibility: 42 | 43 | 1. For iOS: Impeller is enabled by default on iOS in Flutter 3.10+ 44 | 2. For Android: Enable Impeller in your app by adding the following to your `AndroidManifest.xml`: 45 | ```xml 46 | 49 | ``` 50 | 3. For macOS/desktop: Launch your app with the `--enable-impeller` flag 51 | 52 | Attempting to use this package with Skia will result in rendering errors or crashes. 53 | 54 | ## Usage 📱 55 | 56 | Liquido provides several ways to create glass effects in your Flutter UI. Check out the example app for comprehensive usage demonstrations. 57 | 58 | ### Basic Glass Container 59 | 60 | Create a container with glass effect: 61 | 62 | ```dart 63 | Glass( 64 | blurSigma: 20, 65 | refractionBorder: 2, 66 | saturationBoost: 1.1, 67 | centerScale: 0.8, 68 | brightnessCompensation: -0.1, 69 | shape: RoundedSuperellipseBorder( 70 | borderRadius: BorderRadius.all( 71 | Radius.circular(66), 72 | ), 73 | ), 74 | child: SizedBox( 75 | width: 200, 76 | height: 100, 77 | ), 78 | ) 79 | ``` 80 | 81 | ### Glass Text 82 | 83 | Create text with a glass effect: 84 | 85 | ```dart 86 | Glass.text( 87 | 'Hello Glass', 88 | textAlign: TextAlign.center, 89 | style: GoogleFonts.nunito( 90 | fontSize: 60, 91 | fontWeight: FontWeight.w900, 92 | ), 93 | blurSigma: 35, 94 | saturationBoost: 0.7, 95 | brightnessCompensation: 0.2, 96 | refractionBorder: 3, 97 | ) 98 | ``` 99 | 100 | ### Custom Glass Shape 101 | 102 | Create a glass effect with a custom shape: 103 | 104 | ```dart 105 | Glass.custom( 106 | mask: YourCustomShapeWidget(), 107 | child: YourContentWidget(), 108 | blurSigma: 15, 109 | refractionBorder: 4, 110 | glassTint: Colors.blue.withOpacity(0.1), 111 | ) 112 | ``` 113 | 114 | ## Customization Options 🎨 115 | 116 | Liquido offers many customization options to fine-tune your glass effect: 117 | 118 | | Parameter | Description | Recommended Range | 119 | |-----------|-------------|------------------| 120 | | `blurSigma` | The amount of blur applied to the glass effect | 0-40 | 121 | | `contrastBoost` | The amount of contrast applied to the glass effect | 1-1.3 | 122 | | `saturationBoost` | The amount of saturation applied to the glass effect | 1-1.3 | 123 | | `grainIntensity` | The amount of grain applied to the glass effect | 0-1 | 124 | | `brightnessCompensation` | The brightness adjustment of the glass | -1.0 to 1.0 | 125 | | `centerScale` | The scaling factor for the center of the glass effect | 0.1-1.5 | 126 | | `edgeScale` | The scaling factor for the edges of the glass effect | 0.1-1.5 | 127 | | `glassTint` | The color tint applied to the glass effect | Any color with alpha | 128 | | `refractionBorder` | The width of the refraction border in pixels | 0-half of surface size | 129 | | `boxShadow` | The shadow applied around the glass effect | Any BoxShadow | 130 | 131 | ## Example App 132 | 133 | For a complete demonstration of Liquido's capabilities, check out the example app in the `/example` directory. It showcases various glass effects, including: 134 | 135 | - Glass shapes with customized parameters 136 | - Animated clock with glass effect 137 | - Interactive glass buttons 138 | - Animated background gradients behind glass surfaces 139 | 140 | 141 | ## Known Limitations ⚠️ 142 | 143 | This package has several limitations, and there is no guarantee that these issues will ever be addressed or resolved as this is primarily a technology demonstration: 144 | 145 | - **Impeller only**: Does not work with Skia rendering backend 146 | - Not optimized for performance - may cause frame drops on complex UIs 147 | - Works best with static or slowly animating content 148 | - High blur values can be computationally expensive 149 | - Shader compilation may cause a brief delay on first render 150 | - Not all platforms support Impeller equally well - iOS has the best support 151 | - May break with future Flutter updates as it relies on implementation details 152 | 153 | ## License 154 | 155 | This project is licensed under the Mozilla Public License 2.0 (MPL-2.0) - see the [LICENSE](LICENSE) file for details. 156 | 157 | --- 158 | 159 | [license_badge]: https://img.shields.io/badge/license-MPL-green.svg 160 | [license_link]: https://opensource.org/license/mpl-2-0 161 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 28 | 29 | # Contribution Guidelines 30 | 31 | **Note:** If these contribution guidelines are not followed your issue or PR might be closed, so please read these instructions carefully. 32 | 33 | 34 | ## Contribution types 35 | 36 | 37 | ### Bug Reports 38 | 39 | - If you find a bug, please first report it using [Github issues]. 40 | - First check if there is not already an issue for it; duplicated issues will be closed. 41 | 42 | 43 | ### Bug Fix 44 | 45 | - If you'd like to submit a fix for a bug, please read the [How To](#how-to-contribute) for how to 46 | send a Pull Request. 47 | - Indicate on the open issue that you are working on fixing the bug and the issue will be assigned 48 | to you. 49 | - Write `Fixes #xxxx` in your PR text, where xxxx is the issue number (if there is one). 50 | - Include a test that isolates the bug and verifies that it was fixed. 51 | 52 | 53 | ### New Features 54 | 55 | - If you'd like to add a feature to the library that doesn't already exist, feel free to describe the feature in a new [GitHub issue]. 56 | - If you'd like to implement the new feature, please wait for feedback from the project maintainers 57 | before spending too much time writing the code. In some cases, enhancements may not align well 58 | with the project future development direction. 59 | - Implement the code for the new feature and please read the [How To](#how-to-contribute). 60 | 61 | 62 | ### Documentation & Miscellaneous 63 | 64 | - If you have suggestions for improvements to the documentation, tutorial or examples (or something 65 | else), we would love to hear about it. 66 | - As always first file a [Github issue]. 67 | - Implement the changes to the documentation, please read the [How To](#how-to-contribute). 68 | 69 | 70 | ## How To Contribute 71 | 72 | ### Requirements 73 | 74 | For a contribution to be accepted: 75 | 76 | - Format the code using `dart format .`; 77 | - Lint the code with the proper tool; 78 | - Check that all tests pass; 79 | - Documentation should always be updated or added (if applicable); 80 | - Examples should always be updated or added (if applicable); 81 | - Tests should always be updated or added (if applicable); 82 | - The PR title should start with a [conventional commit] prefix (`feat:`, `fix:` etc). 83 | 84 | If the contribution doesn't meet these criteria, a maintainer will discuss it with you on the issue or PR. You can still continue to add more commits to the branch you have sent the Pull Request from and it will be automatically reflected in the PR. 85 | 86 | 87 | ## Open an issue and fork the repository 88 | 89 | - If it is a bigger change or a new feature, first of all 90 | [file a bug or feature report][GitHub issue], so that we can discuss what direction to follow. 91 | - [Fork the project][fork guide] on GitHub. 92 | - Clone the forked repository to your local development machine 93 | (e.g. `git clone git@github.com:/omesh.git`). 94 | 95 | 96 | ### Performing changes 97 | 98 | - Create a new local branch from `main` (e.g. `git checkout -b my-new-feature`) 99 | - Make your changes (try to split them up with one PR per feature/fix). 100 | - When committing your changes, make sure that each commit message is clear 101 | (e.g. `git commit -m 'Make mesh gradients look decent'`). 102 | - Push your new branch to your own fork into the same remote branch 103 | (e.g. `git push origin my-username.my-new-feature`, replace `origin` if you use another remote.) 104 | 105 | 106 | ### Open a pull request 107 | 108 | Go to the [pull request page][PRs] and in the top 109 | of the page it will ask you if you want to open a pull request from your newly created branch. 110 | 111 | The title of the pull request should start with a [conventional commit] type. 112 | 113 | Allowed types are: 114 | 115 | - `fix:` -- patches a bug and is not a new feature; 116 | - `feat:` -- introduces a new feature; 117 | - `docs:` -- updates or adds documentation or examples; 118 | - `test:` -- updates or adds tests; 119 | - `refactor:` -- refactors code but doesn't introduce any changes or additions to the public API; 120 | - `perf:` -- code change that improves performance; 121 | - `build:` -- code change that affects the build system or external dependencies; 122 | - `ci:` -- changes to the CI configuration files and scripts; 123 | - `chore:` -- other changes that don't modify source or test files; 124 | - `revert:` -- reverts a previous commit. 125 | 126 | If you introduce a **breaking change** the conventional commit type MUST end with an exclamation 127 | mark (e.g. `feat!: Remove the position argument from PositionComponent`). 128 | 129 | Examples of PR titles: 130 | 131 | - feat: Maake mesh gradients great again 132 | - fix(#123): Fix this bug on the issue # 123 133 | - docs: Make documentation decent 134 | - test: Add some tests to that feature 135 | 136 | 137 | [GitHub issue]: https://github.com/renancaraujo/omesh/issues 138 | [GitHub issues]: https://github.com/renancaraujo/omesh/issues 139 | [fork guide]: https://docs.github.com/en/get-started/quickstart/contributing-to-projects 140 | [conventional commit]: https://www.conventionalcommits.org 141 | [PRs]: https://github.com/renancaraujo/omesh/pulls 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /shaders/liquido_impeller.frag: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025, Renan Araujo 2 | // Use of this source code is governed by a MPL-2.0 license that can be 3 | // found in the LICENSE file. 4 | 5 | #version 460 core 6 | 7 | precision highp float; 8 | 9 | #include 10 | 11 | uniform vec2 uSize; 12 | uniform vec4 uComponentRect; 13 | uniform float uBlurSigma; 14 | uniform float uContrastBoost; 15 | uniform float uSaturationBoost; 16 | uniform float uGrainIntensity; 17 | uniform float uBrightnessCompensation; 18 | uniform float uEdgeScale; 19 | uniform float uCenterScale; 20 | uniform vec4 uGlassTint; 21 | 22 | uniform sampler2D uTextureInput; 23 | uniform sampler2D uMask; 24 | uniform sampler2D uBlurMask; 25 | uniform sampler2D uSecondaryBlurMask; 26 | 27 | out vec4 fragColor; 28 | 29 | #define PI 3.14159265358979323846 30 | 31 | float hash(vec2 p) { 32 | p = fract(p * vec2(123.34, 456.21)); 33 | p += dot(p, p + 45.32); 34 | return fract(p.x * p.y); 35 | } 36 | 37 | float noise(vec2 p) { 38 | vec2 i = floor(p); 39 | vec2 f = fract(p); 40 | vec2 u = f * f * (3.0 - 2.0 * f); 41 | 42 | return mix(mix(hash(i), hash(i + vec2(1.0, 0.0)), u.x), mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), u.y); 43 | } 44 | 45 | vec4 blurAndOtherStuff(vec2 uv, float edgeStr) { 46 | float blurRadius = uBlurSigma ; 47 | 48 | 49 | const int samples = 6; 50 | const float gamma = 2.2; 51 | const float invGamma = 1.0 / gamma; 52 | const float grainScale = 1.2; 53 | const float grainSharp = 0.9; 54 | const float grainDistAmt = 8.4; 55 | 56 | vec4 origColor = texture(uTextureInput, uv); 57 | vec2 grainCoord = uv * uSize * grainScale; 58 | float grainNoise = noise(grainCoord); 59 | 60 | grainNoise = pow(abs(grainNoise * 2.0 - 1.0), grainSharp) * sign(grainNoise - 0.5) * 0.5 + 0.5; 61 | float fineGrain = noise(grainCoord * 2.0) * 0.4 + noise(grainCoord * 4.0) * 0.3; 62 | float grain = mix(grainNoise, fineGrain, 0.3) * uGrainIntensity; 63 | 64 | if(blurRadius < 0.1) { 65 | 66 | float grainBrightnessCompensation = 0.1; 67 | 68 | vec3 result = origColor.rgb + vec3(grain * 0.2 - 0.1 + grainBrightnessCompensation); 69 | 70 | return vec4(result, origColor.a); 71 | 72 | } 73 | 74 | vec3 blurColor = vec3(0.0); 75 | float totalWeight = 0.0; 76 | 77 | for(int y = -samples; y <= samples; ++y) { 78 | for(int x = -samples; x <= samples; ++x) { 79 | vec2 samplePos = vec2(float(x), float(y)) / float(samples); 80 | float localGrain = noise((samplePos + uv) * uSize * grainScale * 0.5) * uGrainIntensity; 81 | 82 | vec2 grainDist = vec2(noise((samplePos + uv) * uSize * 0.2), noise((samplePos + uv) * uSize * 0.2 + vec2(3.14, 2.71))) * 2.0 - 1.0; 83 | 84 | vec2 offset = vec2(x, y); 85 | offset += grainDist * grain * grainDistAmt; 86 | offset *= (blurRadius / float(samples)) / uSize; 87 | 88 | float dist = length(vec2(x, y)) / float(samples); 89 | float grainFactor = mix(0.8, 1.2, localGrain); 90 | float weight = exp(-dist * dist * grainFactor * 5.0); 91 | 92 | if(weight > 0.001) { 93 | blurColor += pow(texture(uTextureInput, uv + offset).rgb, vec3(gamma)) * weight; 94 | totalWeight += weight; 95 | } 96 | } 97 | } 98 | 99 | blurColor /= totalWeight; 100 | 101 | vec3 avgColor = vec3(0.5); 102 | blurColor = avgColor + (blurColor - avgColor) * uContrastBoost; 103 | 104 | float lum = dot(blurColor, vec3(0.299, 0.587, 0.114)); 105 | blurColor = mix(vec3(lum), blurColor, uSaturationBoost); 106 | blurColor = mix(blurColor, blurColor * (1.0 + grain * 0.2), grain * 0.5); 107 | 108 | vec3 srgbColor = pow(blurColor, vec3(invGamma)); 109 | float grainEffect = grain * 0.2 - 0.075 + 0.1; 110 | 111 | return vec4(srgbColor + vec3(grainEffect), origColor.a); 112 | } 113 | 114 | float softLightChannel(float base, float blend) { 115 | return blend < 0.5 ? base - (1.0 - 2.0 * blend) * base * (1.0 - base) : base + (2.0 * blend - 1.0) * (sqrt(base) - base); 116 | } 117 | 118 | vec3 softLight(vec3 base, vec3 blend) { 119 | return vec3(softLightChannel(base.r, blend.r), softLightChannel(base.g, blend.g), softLightChannel(base.b, blend.b)); 120 | } 121 | 122 | void inMaskRegion(vec2 pos, inout vec4 color) { 123 | vec2 uv = pos / uSize; 124 | vec2 uvComp = (pos - uComponentRect.xy) / uComponentRect.zw; 125 | vec2 compCenter = (uComponentRect.xy + uComponentRect.zw * 0.5) / uSize; 126 | 127 | float dCenter = length(uvComp - 0.5); 128 | float t = smoothstep(0.0, 1.0, clamp(1.0 - dCenter, 0.0, 1.0)); 129 | float magFactor = mix(1.0 / uEdgeScale, 1.0 / uCenterScale, t); 130 | 131 | vec2 magPos = (uv - compCenter) * magFactor + compCenter; 132 | 133 | float edgeStr = 1.0 - texture(uBlurMask, uvComp).a; 134 | 135 | edgeStr = pow(clamp(edgeStr, 0.0, 1.0), 0.9); 136 | edgeStr = clamp(edgeStr, 0.0, 0.7) * 1.42857; 137 | edgeStr *= pow(dCenter, .1); 138 | 139 | float angle = -atan(uvComp.y - 0.5, uvComp.x - 0.5); 140 | vec2 distUV = magPos + vec2(-cos(angle), sin(angle)) * 0.08 * edgeStr; 141 | 142 | color = blurAndOtherStuff(distUV, edgeStr); 143 | 144 | float colorIntensity = uGlassTint.a; 145 | colorIntensity += colorIntensity * edgeStr; 146 | 147 | float postEdgeStr = 1.0 - texture(uSecondaryBlurMask, uvComp).a; 148 | vec3 softLightBlend = softLight(color.rgb, uGlassTint.rgb); 149 | color.rgb = mix(color.rgb, softLightBlend, colorIntensity); 150 | 151 | float brightComp = uBrightnessCompensation; 152 | float brightBoost = PI / 9.0 * abs(brightComp); 153 | float brightDir = brightComp < 0.0 ? -1.0 : 1.0; 154 | 155 | brightBoost += brightBoost * postEdgeStr; 156 | color.rgb = mix(color.rgb, vec3(brightDir), brightBoost); 157 | 158 | float shineFactor = postEdgeStr * clamp(sin((angle + PI / 1.8) * 1.8), 0.0, 1.0) * dCenter; 159 | color.rgb = mix(color.rgb, vec3(1.0), shineFactor * 0.5); 160 | 161 | float shadowFactor = postEdgeStr * clamp(cos((angle + PI / -2.7) * 2.0), 0.0, 1.0) * dCenter; 162 | color.rgb = mix(color.rgb, vec3(0.0), shadowFactor * 0.5); 163 | } 164 | 165 | void fragment(vec2 pos, inout vec4 color) { 166 | vec2 uvComp = (pos - uComponentRect.xy) / uComponentRect.zw; 167 | 168 | if(all(greaterThanEqual(uvComp, vec2(0.0))) && all(lessThanEqual(uvComp, vec2(1.0)))) 169 | if(texture(uMask, uvComp).a > 0.0) 170 | inMaskRegion(pos, color); 171 | } 172 | 173 | void main() { 174 | vec2 pos = FlutterFragCoord().xy; 175 | vec4 color; 176 | fragment(pos, color); 177 | fragColor = color; 178 | } 179 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | language: 3 | strict-casts: true 4 | strict-inference: true 5 | strict-raw-types: true 6 | 7 | errors: 8 | close_sinks: ignore 9 | missing_required_param: error 10 | missing_return: error 11 | record_literal_one_positional_no_trailing_comma: error 12 | collection_methods_unrelated_type: warning 13 | unrelated_type_equality_checks: warning 14 | 15 | exclude: 16 | - test/.test_coverage.dart 17 | - lib/generated_plugin_registrant.dart 18 | 19 | formatter: 20 | trailing_commas: preserve 21 | 22 | linter: 23 | rules: 24 | - always_declare_return_types 25 | - always_put_required_named_parameters_first 26 | - always_use_package_imports 27 | - annotate_overrides 28 | - avoid_bool_literals_in_conditional_expressions 29 | - avoid_catching_errors 30 | - avoid_double_and_int_checks 31 | - avoid_dynamic_calls 32 | - avoid_empty_else 33 | - avoid_escaping_inner_quotes 34 | - avoid_field_initializers_in_const_classes 35 | - avoid_final_parameters 36 | - avoid_function_literals_in_foreach_calls 37 | - avoid_init_to_null 38 | - avoid_js_rounded_ints 39 | - avoid_multiple_declarations_per_line 40 | - avoid_positional_boolean_parameters 41 | - avoid_print 42 | - avoid_private_typedef_functions 43 | - avoid_redundant_argument_values 44 | - avoid_relative_lib_imports 45 | - avoid_renaming_method_parameters 46 | - avoid_return_types_on_setters 47 | - avoid_returning_null_for_void 48 | - avoid_returning_this 49 | - avoid_shadowing_type_parameters 50 | - avoid_single_cascade_in_expression_statements 51 | - avoid_slow_async_io 52 | - avoid_type_to_string 53 | - avoid_types_as_parameter_names 54 | - avoid_unnecessary_containers 55 | - avoid_unused_constructor_parameters 56 | - avoid_void_async 57 | - avoid_web_libraries_in_flutter 58 | - await_only_futures 59 | - camel_case_extensions 60 | - camel_case_types 61 | - cancel_subscriptions 62 | - cascade_invocations 63 | - cast_nullable_to_non_nullable 64 | - collection_methods_unrelated_type 65 | - combinators_ordering 66 | - comment_references 67 | - conditional_uri_does_not_exist 68 | - constant_identifier_names 69 | - control_flow_in_finally 70 | - curly_braces_in_flow_control_structures 71 | - dangling_library_doc_comments 72 | - depend_on_referenced_packages 73 | - deprecated_consistency 74 | - directives_ordering 75 | - document_ignores 76 | - empty_catches 77 | - empty_constructor_bodies 78 | - empty_statements 79 | - eol_at_end_of_file 80 | - exhaustive_cases 81 | - file_names 82 | - flutter_style_todos 83 | - hash_and_equals 84 | - implicit_call_tearoffs 85 | - implementation_imports 86 | - implicit_reopen 87 | - invalid_case_patterns 88 | - invalid_runtime_check_with_js_interop_types 89 | - join_return_with_assignment 90 | - leading_newlines_in_multiline_strings 91 | - library_annotations 92 | - library_prefixes 93 | - library_private_types_in_public_api 94 | - lines_longer_than_80_chars 95 | - literal_only_boolean_expressions 96 | - missing_code_block_language_in_doc_comment 97 | - missing_whitespace_between_adjacent_strings 98 | - no_adjacent_strings_in_list 99 | - no_default_cases 100 | - no_duplicate_case_values 101 | - no_leading_underscores_for_library_prefixes 102 | - no_leading_underscores_for_local_identifiers 103 | - no_logic_in_create_state 104 | - no_runtimeType_toString 105 | - no_self_assignments 106 | - no_wildcard_variable_uses 107 | - non_constant_identifier_names 108 | - noop_primitive_operations 109 | - null_check_on_nullable_type_parameter 110 | - null_closures 111 | - omit_local_variable_types 112 | - one_member_abstracts 113 | - only_throw_errors 114 | - overridden_fields 115 | - package_names 116 | - parameter_assignments 117 | - prefer_adjacent_string_concatenation 118 | - prefer_asserts_in_initializer_lists 119 | - prefer_asserts_with_message 120 | - prefer_collection_literals 121 | - prefer_conditional_assignment 122 | - prefer_const_constructors 123 | - prefer_const_constructors_in_immutables 124 | - prefer_const_declarations 125 | - prefer_const_literals_to_create_immutables 126 | - prefer_constructors_over_static_methods 127 | - prefer_contains 128 | - prefer_final_fields 129 | - prefer_final_in_for_each 130 | - prefer_final_locals 131 | - prefer_for_elements_to_map_fromIterable 132 | - prefer_function_declarations_over_variables 133 | - prefer_generic_function_type_aliases 134 | - prefer_if_elements_to_conditional_expressions 135 | - prefer_if_null_operators 136 | - prefer_initializing_formals 137 | - prefer_inlined_adds 138 | - prefer_int_literals 139 | - prefer_interpolation_to_compose_strings 140 | - prefer_is_empty 141 | - prefer_is_not_empty 142 | - prefer_is_not_operator 143 | - prefer_iterable_whereType 144 | - prefer_null_aware_method_calls 145 | - prefer_null_aware_operators 146 | - prefer_single_quotes 147 | - prefer_spread_collections 148 | - prefer_typing_uninitialized_variables 149 | - provide_deprecation_message 150 | - public_member_api_docs 151 | - recursive_getters 152 | - secure_pubspec_urls 153 | - sized_box_for_whitespace 154 | - sized_box_shrink_expand 155 | - slash_for_doc_comments 156 | - sort_child_properties_last 157 | - sort_constructors_first 158 | - sort_pub_dependencies 159 | - sort_unnamed_constructors_first 160 | - specify_nonobvious_property_types 161 | - strict_top_level_inference 162 | - test_types_in_equals 163 | - throw_in_finally 164 | - tighten_type_of_initializing_formals 165 | - type_annotate_public_apis 166 | - type_init_formals 167 | - type_literal_in_constant_pattern 168 | - unawaited_futures 169 | - unintended_html_in_doc_comment 170 | - unnecessary_await_in_return 171 | - unnecessary_breaks 172 | - unnecessary_brace_in_string_interps 173 | - unnecessary_const 174 | - unnecessary_constructor_name 175 | - unnecessary_getters_setters 176 | - unnecessary_lambdas 177 | - unnecessary_late 178 | - unnecessary_library_directive 179 | - unnecessary_library_name 180 | - unnecessary_new 181 | - unnecessary_null_aware_assignments 182 | - unnecessary_null_checks 183 | - unnecessary_null_in_if_null_operators 184 | - unnecessary_nullable_for_final_variable_declarations 185 | - unnecessary_overrides 186 | - unnecessary_parenthesis 187 | - unnecessary_raw_strings 188 | - unnecessary_statements 189 | - unnecessary_string_escapes 190 | - unnecessary_string_interpolations 191 | - unnecessary_this 192 | - unnecessary_to_list_in_spreads 193 | - unnecessary_underscores 194 | - unrelated_type_equality_checks 195 | - use_build_context_synchronously 196 | - use_colored_box 197 | - use_enums 198 | - use_full_hex_values_for_flutter_colors 199 | - use_function_type_syntax_for_parameters 200 | - use_if_null_to_convert_nulls_to_bools 201 | - use_is_even_rather_than_modulo 202 | - use_key_in_widget_constructors 203 | - use_late_for_private_fields_and_variables 204 | - use_named_constants 205 | - use_raw_strings 206 | - use_rethrow_when_possible 207 | - use_setters_to_change_properties 208 | - use_string_buffers 209 | - use_string_in_part_of_directives 210 | - use_super_parameters 211 | - use_test_throws_matchers 212 | - use_to_and_as_if_applicable 213 | - valid_regexps 214 | - void_checks 215 | -------------------------------------------------------------------------------- /lib/src/glass_options.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025, Renan Araujo 2 | // Use of this source code is governed by a MPL-2.0 license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:ui' as ui; 6 | 7 | import 'package:equatable/equatable.dart'; 8 | import 'package:flutter/rendering.dart'; 9 | 10 | /// A common interface for the parameters used in the glass effect. 11 | /// 12 | /// This interface defines the properties that can be used to customize the 13 | /// glass effect, such as blur, contrast, saturation, grain intensity, 14 | /// 15 | /// Properties: 16 | /// {@template glass_options} 17 | /// Things you may want to change most often are: 18 | /// [blurSigma], [refractionBorder], [brightnessCompensation], 19 | /// [glassTint], and [boxShadow]. 20 | /// {@endtemplate} 21 | /// 22 | /// See also; 23 | /// - [GlassOptions.defaultOptions], the default values for these 24 | /// properties. 25 | /// - [GlassOptions], a concrete, lerpable implementation of this interface. 26 | abstract interface class GlassOptionsInterface { 27 | /// The amount of blur applied to the glass effect. 28 | /// The recommended value is between 0 and 40. 0 means no blur. Higher values 29 | /// may result in artifacts. 30 | /// See default on [GlassOptions.defaultOptions]. 31 | double? get blurSigma; 32 | 33 | /// The amount of contrast applied to the glass effect. 34 | /// The recommended value is between 1 and 1.3. 1 means no contrast boost. 35 | /// Higher values may result in artifacts. 36 | /// See default on [GlassOptions.defaultOptions]. 37 | double? get contrastBoost; 38 | 39 | /// The amount of saturation applied to the glass effect. 40 | /// The recommended value is between 1 and 1.3. 1 means no saturation boost. 41 | /// Higher values may result in artifacts. 42 | /// See default on [GlassOptions.defaultOptions]. 43 | double? get saturationBoost; 44 | 45 | /// The amount of grain applied to the glass effect. 46 | /// The recommended value is between 0 and 1. 0 means no grain. 47 | /// See default on [GlassOptions.defaultOptions]. 48 | double? get grainIntensity; 49 | 50 | /// The amount of brightness compensation applied to the glass effect. 51 | /// The recommended value is between -1.0 and 1.0. 0 means no brightness 52 | /// compensation, -1 means full dark glass, 1 means full bright glass. 53 | /// Higher or lower values may result in artifacts. 54 | /// See default on [GlassOptions.defaultOptions]. 55 | double? get brightnessCompensation; 56 | 57 | /// The amount of scaling applied to the center of the glass effect. 58 | /// 1.0 means no scaling, 2.0 means double the size of the center. 59 | /// The recommended value is between 0.1 and 1.5. 60 | /// See default on [GlassOptions.defaultOptions]. 61 | double? get centerScale; 62 | 63 | /// The amount of scaling applied to the edges of the glass effect. 64 | /// 1.0 means no scaling, 2.0 means double the size of the edges. 65 | /// The recommended value is between 0.1 and 1.5. 66 | /// See default on [GlassOptions.defaultOptions]. 67 | double? get edgeScale; 68 | 69 | /// The color tint applied to the glass effect. 70 | /// The recommended value is a color with an alpha value between 0 and 1. 71 | /// 0 means no tint, 1 means full tint. 72 | /// See default on [GlassOptions.defaultOptions]. 73 | Color? get glassTint; 74 | 75 | /// The width of the refraction border applied to the edges of the glass 76 | /// effect in pixels. 77 | /// The recommended value is to never exceed half the size (width or height) 78 | /// of the glassed surface. 79 | /// 0 means no refraction border. 80 | /// See default on [GlassOptions.defaultOptions]. 81 | double? get refractionBorder; 82 | 83 | /// The shadow applied around the glass effect. 84 | /// Null means no shadow. See [BoxShadow] for more information. 85 | /// See default on [GlassOptions.defaultOptions]. 86 | BoxShadow? get boxShadow; 87 | } 88 | 89 | /// A concrete implementation of [GlassOptionsInterface] that provides default 90 | /// values for the glass effect parameters. 91 | /// 92 | /// See also: 93 | /// - [GlassOptionsInterface] for more information on the 94 | /// properties. 95 | /// - [GlassOptions.defaultOptions] for the default values of these 96 | /// properties. 97 | class GlassOptions with EquatableMixin implements GlassOptionsInterface { 98 | /// Creates a new instance of [GlassOptions] with the specified parameters. 99 | /// 100 | /// All parameters are required to ensure a fully configured glass effect. 101 | /// For default values, see [GlassOptions.defaultOptions]. 102 | /// 103 | /// Example: 104 | /// ```dart 105 | /// final customGlass = GlassOptions( 106 | /// blurSigma: 15.0, 107 | /// contrastBoost: 1.1, 108 | /// saturationBoost: 1.1, 109 | /// grainIntensity: 0.2, 110 | /// brightnessCompensation: 0.5, 111 | /// centerScale: 1.0, 112 | /// edgeScale: 1.0, 113 | /// glassTint: Colors.blue.withOpacity(0.1), 114 | /// refractionBorder: 10.0, 115 | /// boxShadow: BoxShadow( 116 | /// color: Colors.black.withOpacity(0.2), 117 | /// blurRadius: 10, 118 | /// spreadRadius: 2, 119 | /// ), 120 | /// ); 121 | /// ``` 122 | const GlassOptions({ 123 | required this.blurSigma, 124 | required this.contrastBoost, 125 | required this.saturationBoost, 126 | required this.grainIntensity, 127 | required this.brightnessCompensation, 128 | required this.centerScale, 129 | required this.edgeScale, 130 | required this.glassTint, 131 | required this.refractionBorder, 132 | required this.boxShadow, 133 | }); 134 | 135 | /// Creates a new instance of [GlassOptions] from an existing 136 | /// [GlassOptionsInterface]. 137 | /// 138 | /// This constructor provides a convenient way to convert any implementation 139 | /// of [GlassOptionsInterface] into a concrete [GlassOptions] instance. 140 | /// It fills in any null values with the corresponding values from 141 | /// [defaultOptions]. 142 | /// 143 | /// This is useful when you want to ensure all properties have non-null 144 | /// values, or when you want to convert a partial configuration into a 145 | /// complete one. 146 | /// 147 | /// Example: 148 | /// ```dart 149 | /// // Some partial implementation of GlassOptionsInterface 150 | /// final partialOptions = MyCustomGlassOptions(); 151 | /// 152 | /// // Convert to a complete GlassOptions instance 153 | /// final completeOptions = GlassOptions.fromInterface(partialOptions); 154 | /// ``` 155 | GlassOptions.fromInterface( 156 | GlassOptionsInterface options, 157 | ) : this( 158 | blurSigma: options.blurSigma ?? defaultOptions.blurSigma, 159 | contrastBoost: options.contrastBoost ?? defaultOptions.contrastBoost, 160 | saturationBoost: 161 | options.saturationBoost ?? defaultOptions.saturationBoost, 162 | grainIntensity: options.grainIntensity ?? defaultOptions.grainIntensity, 163 | brightnessCompensation: 164 | options.brightnessCompensation ?? 165 | defaultOptions.brightnessCompensation, 166 | centerScale: options.centerScale ?? defaultOptions.centerScale, 167 | edgeScale: options.edgeScale ?? defaultOptions.edgeScale, 168 | glassTint: options.glassTint ?? defaultOptions.glassTint, 169 | refractionBorder: 170 | options.refractionBorder ?? defaultOptions.refractionBorder, 171 | boxShadow: options.boxShadow ?? defaultOptions.boxShadow, 172 | ); 173 | 174 | /// The default options for the glass effect. 175 | /// 176 | /// These values are recommended for most use cases and provide a good 177 | /// starting point for customizing the glass effect. 178 | /// 179 | /// Things you may want to change most often are: 180 | /// [blurSigma], [refractionBorder], [brightnessCompensation], 181 | /// [glassTint], and [boxShadow]. 182 | static const GlassOptions defaultOptions = GlassOptions( 183 | blurSigma: 10, 184 | contrastBoost: 1, 185 | saturationBoost: 1, 186 | grainIntensity: 0, 187 | brightnessCompensation: 0, 188 | centerScale: 1, 189 | edgeScale: 1, 190 | glassTint: null, 191 | refractionBorder: 0, 192 | boxShadow: null, 193 | ); 194 | 195 | @override 196 | final double blurSigma; 197 | @override 198 | final double contrastBoost; 199 | @override 200 | final double saturationBoost; 201 | @override 202 | final double grainIntensity; 203 | @override 204 | final double brightnessCompensation; 205 | @override 206 | final double centerScale; 207 | @override 208 | final double edgeScale; 209 | @override 210 | final Color? glassTint; 211 | @override 212 | final double refractionBorder; 213 | @override 214 | final BoxShadow? boxShadow; 215 | 216 | /// Creates a new [GlassOptions] by linearly interpolating between 217 | /// two instances. 218 | /// 219 | /// If either `a` or `b` is null, this function returns the non-null instance. 220 | /// If both are null, it returns null. 221 | /// 222 | /// The `t` argument represents the position on the timeline, with 0.0 meaning 223 | /// this function returns `a`, and 1.0 meaning this function returns `b`. 224 | static GlassOptionsInterface? lerp( 225 | GlassOptionsInterface? a, 226 | GlassOptionsInterface? b, 227 | double t, 228 | ) { 229 | if (a == null && b == null) return null; 230 | if (a == null) return b; 231 | if (b == null) return a; 232 | if (t == 0.0) return a; 233 | if (t == 1.0) return b; 234 | 235 | return GlassOptions( 236 | blurSigma: ui.lerpDouble(a.blurSigma, b.blurSigma, t)!, 237 | contrastBoost: ui.lerpDouble(a.contrastBoost, b.contrastBoost, t)!, 238 | saturationBoost: ui.lerpDouble(a.saturationBoost, b.saturationBoost, t)!, 239 | grainIntensity: ui.lerpDouble(a.grainIntensity, b.grainIntensity, t)!, 240 | brightnessCompensation: ui.lerpDouble( 241 | a.brightnessCompensation, 242 | b.brightnessCompensation, 243 | t, 244 | )!, 245 | centerScale: ui.lerpDouble(a.centerScale, b.centerScale, t)!, 246 | edgeScale: ui.lerpDouble(a.edgeScale, b.edgeScale, t)!, 247 | glassTint: Color.lerp(a.glassTint, b.glassTint, t), 248 | refractionBorder: ui.lerpDouble( 249 | a.refractionBorder, 250 | b.refractionBorder, 251 | t, 252 | )!, 253 | boxShadow: BoxShadow.lerp(a.boxShadow, b.boxShadow, t), 254 | ); 255 | } 256 | 257 | @override 258 | List get props => [ 259 | blurSigma, 260 | contrastBoost, 261 | saturationBoost, 262 | grainIntensity, 263 | brightnessCompensation, 264 | centerScale, 265 | edgeScale, 266 | glassTint, 267 | refractionBorder, 268 | boxShadow, 269 | ]; 270 | } 271 | 272 | /// Extension that provides convenient linear interpolation methods for 273 | /// [GlassOptionsInterface] instances. 274 | /// 275 | /// This extension adds methods for interpolating between two 276 | /// [GlassOptionsInterface] instances, making it easier to create animations 277 | /// or transitions between different glass effect configurations. 278 | /// 279 | /// See also: 280 | /// - [GlassOptionsInterface] for the interface that this extension applies to. 281 | /// - [GlassOptions.lerp] for the static method used internally by this 282 | /// extension. 283 | extension GlassOptionsLerp on GlassOptionsInterface { 284 | /// Creates a new [GlassOptions] that is a copy of this instance but with the 285 | /// given fields replaced with the values interpolated between this instance 286 | /// and another. 287 | /// 288 | /// The `t` argument represents the position on the timeline, with 0.0 meaning 289 | /// this function returns `this`, and 1.0 meaning this function returns 290 | /// `other`. 291 | GlassOptionsInterface? lerpTo(GlassOptionsInterface? other, double t) { 292 | if (other == null) return this; 293 | return GlassOptions.lerp(this, other, t); 294 | } 295 | 296 | /// Creates a new [GlassOptions] that is a copy of the given instance but with 297 | /// the given fields replaced with the values interpolated between the given 298 | /// instance and this instance. 299 | /// 300 | /// The `t` argument represents the position on the timeline, with 0.0 meaning 301 | /// this function returns `other`, and 1.0 meaning this function returns 302 | /// `this`. 303 | GlassOptionsInterface? lerpFrom(GlassOptionsInterface? other, double t) { 304 | if (other == null) return this; 305 | return GlassOptions.lerp(other, this, t); 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:google_fonts/google_fonts.dart'; 5 | import 'package:mesh/mesh.dart'; 6 | import 'package:liquido/liquido.dart'; 7 | 8 | void main() { 9 | runApp(const MyApp()); 10 | } 11 | 12 | class MyApp extends StatelessWidget { 13 | const MyApp({super.key}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return MaterialApp( 18 | title: 'Liquido Demo', 19 | debugShowCheckedModeBanner: false, 20 | theme: ThemeData( 21 | brightness: Brightness.dark, 22 | scaffoldBackgroundColor: const Color(0xFF04050A), 23 | ), 24 | home: const LiquidoHomePage(), 25 | ); 26 | } 27 | } 28 | 29 | class LiquidoHomePage extends StatefulWidget { 30 | const LiquidoHomePage({super.key}); 31 | 32 | @override 33 | State createState() => _LiquidoHomePageState(); 34 | } 35 | 36 | class _LiquidoHomePageState extends State { 37 | OMeshRect _currentMesh = meshRect1; 38 | late Timer _timer; 39 | double _rotationAngle = -0.2; 40 | bool _showClock = true; 41 | 42 | @override 43 | void initState() { 44 | super.initState(); 45 | _timer = Timer.periodic(const Duration(seconds: 2), (timer) { 46 | setState(() { 47 | _currentMesh = _currentMesh == meshRect1 ? meshRect2 : meshRect1; 48 | _rotationAngle = _rotationAngle == -0.2 ? 0.2 : -0.2; 49 | }); 50 | }); 51 | } 52 | 53 | @override 54 | void dispose() { 55 | _timer.cancel(); 56 | super.dispose(); 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | return Scaffold( 62 | body: Stack( 63 | children: [ 64 | Positioned.fill( 65 | child: FittedBox( 66 | fit: BoxFit.cover, 67 | child: SizedBox( 68 | width: 1600, 69 | height: 1200, 70 | child: AnimatedOMeshGradient( 71 | duration: const Duration(seconds: 2), 72 | curve: Curves.easeInOutCubic, 73 | mesh: _currentMesh, 74 | ), 75 | ), 76 | ), 77 | ), 78 | // Only show the "liquido" text when not showing the clock 79 | if (!_showClock) 80 | Center( 81 | child: LayoutBuilder( 82 | builder: (context, constraints) { 83 | final fontSize = constraints.maxWidth < 600 84 | ? constraints.maxWidth * 0.2 85 | : 280.0; 86 | return Padding( 87 | padding: const EdgeInsets.symmetric(horizontal: 16), 88 | child: AnimatedContainer( 89 | duration: const Duration(seconds: 2), 90 | curve: Curves.easeInOutCubic, 91 | transform: Matrix4.rotationZ(_rotationAngle), 92 | transformAlignment: Alignment.center, 93 | child: Text( 94 | 'liquido', 95 | textAlign: TextAlign.center, 96 | overflow: TextOverflow.visible, 97 | softWrap: false, 98 | style: GoogleFonts.nunito( 99 | fontSize: fontSize, 100 | fontWeight: FontWeight.w900, 101 | color: Colors.white, 102 | shadows: [ 103 | Shadow( 104 | color: Colors.black.withOpacity(0.2), 105 | blurRadius: 10, 106 | offset: const Offset(0, 4), 107 | ), 108 | ], 109 | ), 110 | ), 111 | ), 112 | ); 113 | }, 114 | ), 115 | ), 116 | // Display either clock or superellipse based on _showClock state 117 | if (_showClock) GlassTextPage(rotationAngle: _rotationAngle) else SuperellipseGlassPage(rotationAngle: _rotationAngle), 118 | // Toggle button positioned closer to the middle 119 | Positioned( 120 | bottom: MediaQuery.of(context).size.height * 0.3, // Position at 60% from the top 121 | left: 0, 122 | right: 0, 123 | child: Center( 124 | child: GestureDetector( 125 | onTap: () { 126 | setState(() { 127 | _showClock = !_showClock; 128 | }); 129 | }, 130 | child: Glass( 131 | blurSigma: 20, 132 | refractionBorder: 6.5, 133 | 134 | 135 | brightnessCompensation: 0.1, 136 | shape: StadiumBorder(), 137 | boxShadow: BoxShadow( 138 | color: Colors.black.withOpacity(0.2), 139 | blurRadius: 10, 140 | offset: const Offset(0, 4), 141 | ), 142 | child: Padding( 143 | padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), 144 | child: Text( 145 | _showClock ? 'Show Superellipse' : 'Show Clock', 146 | style: GoogleFonts.nunito( 147 | fontWeight: FontWeight.bold, 148 | color: Colors.white, 149 | ), 150 | ), 151 | ), 152 | ), 153 | ), 154 | ), 155 | ), 156 | ], 157 | ), 158 | ); 159 | } 160 | } 161 | 162 | class SuperellipseGlassPage extends StatelessWidget { 163 | final double rotationAngle; 164 | 165 | const SuperellipseGlassPage({ 166 | super.key, 167 | required this.rotationAngle, 168 | }); 169 | 170 | @override 171 | Widget build(BuildContext context) { 172 | return Center( 173 | child: LayoutBuilder( 174 | builder: (context, constraints) { 175 | return const Glass( 176 | key: Key('liquido-glass-superellipse'), 177 | blurSigma: 20, 178 | refractionBorder: 2, 179 | saturationBoost: 1.1, 180 | centerScale: 0.8, 181 | brightnessCompensation: -0.1, 182 | shape: RoundedSuperellipseBorder( 183 | borderRadius: BorderRadius.all( 184 | Radius.circular(66), 185 | ), 186 | ), 187 | child: SizedBox( 188 | width: 200, 189 | height: 100, 190 | ), 191 | ); 192 | }, 193 | ), 194 | ); 195 | } 196 | } 197 | 198 | class GlassTextPage extends StatefulWidget { 199 | final double rotationAngle; 200 | 201 | const GlassTextPage({ 202 | super.key, 203 | required this.rotationAngle, 204 | }); 205 | 206 | @override 207 | State createState() => _GlassTextPageState(); 208 | } 209 | 210 | class _GlassTextPageState extends State { 211 | late String _timeString; 212 | late Timer _timer; 213 | 214 | @override 215 | void initState() { 216 | super.initState(); 217 | _updateTime(); 218 | // Update time every minute 219 | _timer = Timer.periodic(const Duration(minutes: 1), (Timer t) => _updateTime()); 220 | } 221 | 222 | @override 223 | void dispose() { 224 | _timer.cancel(); 225 | super.dispose(); 226 | } 227 | 228 | void _updateTime() { 229 | final now = DateTime.now(); 230 | final hour = now.hour.toString().padLeft(2, '0'); 231 | final minute = now.minute.toString().padLeft(2, '0'); 232 | setState(() { 233 | _timeString = '$hour:$minute'; 234 | }); 235 | } 236 | 237 | @override 238 | Widget build(BuildContext context) { 239 | return Center( 240 | child: LayoutBuilder( 241 | builder: (context, constraints) { 242 | final fontSize = constraints.maxWidth < 600 243 | ? constraints.maxWidth * 0.3 244 | : 280.0; 245 | return Padding( 246 | padding: const EdgeInsets.symmetric(horizontal: 16), 247 | child: Glass.text( 248 | _timeString, 249 | textAlign: TextAlign.center, 250 | overflow: TextOverflow.visible, 251 | softWrap: false, 252 | style: GoogleFonts.nunito( 253 | fontSize: fontSize, 254 | fontWeight: FontWeight.w900, 255 | ), 256 | blurSigma: 35, 257 | // saturationBoost: 0.7, 258 | // brightnessCompensation: 0.2, 259 | refractionBorder: 9, 260 | boxShadow: BoxShadow( 261 | color: Colors.black.withOpacity(0.2), 262 | blurRadius: 10, 263 | offset: const Offset(4, 4), 264 | ), 265 | ), 266 | ); 267 | }, 268 | ), 269 | ); 270 | } 271 | } 272 | 273 | final meshRect1 = OMeshRect( 274 | width: 5, 275 | height: 5, 276 | fallbackColor: const Color(0xffcae3ec), 277 | backgroundColor: const Color(0xffcae3ec), 278 | vertices: [ 279 | (-0.06, -0.01).v, 280 | (0.24, -0.07).v, 281 | (0.51, -0.11).v, 282 | (0.74, -0.05).v, 283 | (1.03, -0.02).v, 284 | (-0.04, 0.34).v, 285 | (0.25, 0.29).v, 286 | (0.46, 0.28).v, 287 | (0.75, 0.25).v, 288 | (1.03, 0.27).v, 289 | (-0.04, 0.45).v, 290 | (0.3, 0.52).v, 291 | (0.68, 0.41).v.bezier( 292 | east: (0.75, 0.4).v, 293 | west: (0.5, 0.44).v, 294 | ), 295 | (0.95, 0.49).v.bezier( 296 | east: (1.03, 0.49).v, 297 | west: (0.82, 0.46).v, 298 | ), 299 | (1.03, 0.4).v, 300 | (-0.04, 0.56).v, 301 | (0.3, 0.59).v.bezier( 302 | east: (0.4, 0.55).v, 303 | west: (0.22, 0.62).v, 304 | ), 305 | (0.68, 0.5).v, 306 | (0.93, 0.6).v.bezier( 307 | north: (0.93, 0.53).v, 308 | ), 309 | (1.03, 0.7).v, 310 | (-0.03, 1.04).v, 311 | (0.35, 1.02).v, 312 | (0.65, 1.06).v, 313 | (0.93, 1.02).v, 314 | (1.07, 1.03).v, 315 | ], 316 | colors: const [ 317 | Color(0xffade3fa), 318 | Color(0xffade3fa), 319 | Color(0xffade3fa), 320 | Color(0xffade3fa), 321 | Color(0xffade3fa), 322 | Color(0xffeef8ff), 323 | Color(0xffd5ebff), 324 | Color(0xffedf5ff), 325 | Color(0xffedf5ff), 326 | Color(0xffedf5ff), 327 | Color(0x73efe8ff), 328 | Color(0xffe8f8ff), 329 | Color(0xffe8f8ff), 330 | Color(0xffe8f8ff), 331 | Color(0xffe8f8ff), 332 | Color(0xff003ebc), 333 | Color(0xff004ac8), 334 | Color(0xff004ac8), 335 | Color(0xff003ebc), 336 | Color(0xff005495), 337 | Color(0xf7020224), 338 | Color(0xf7020224), 339 | Color(0xf7020224), 340 | Color(0xf7020224), 341 | Color(0xf7020224), 342 | ], 343 | ); 344 | 345 | final meshRect2 = OMeshRect( 346 | width: 5, 347 | height: 5, 348 | fallbackColor: const Color(0xffcae3ec), 349 | backgroundColor: const Color(0xffcae3ec), 350 | vertices: [ 351 | (-0.06, -0.01).v, 352 | (0.24, -0.07).v, 353 | (0.51, -0.11).v, 354 | (0.74, -0.05).v, 355 | (1.03, -0.02).v, 356 | (-0.04, 0.34).v, 357 | (0.25, 0.29).v, 358 | (0.46, 0.28).v, 359 | (0.75, 0.25).v, 360 | (1.03, 0.27).v, 361 | (-0.04, 0.45).v, 362 | (0.33, 0.45).v, 363 | (0.67, 0.56).v.bezier( 364 | east: (0.87, 0.58).v, 365 | west: (0.54, 0.52).v, 366 | ), 367 | (0.95, 0.49).v.bezier( 368 | east: (1.03, 0.49).v, 369 | west: (0.82, 0.46).v, 370 | ), 371 | (1.03, 0.4).v, 372 | (-0.04, 0.56).v.bezier( 373 | east: (0.03, 0.53).v, 374 | ), 375 | (0.3, 0.5).v.bezier( 376 | east: (0.44, 0.53).v, 377 | west: (0.22, 0.46).v, 378 | ), 379 | (0.66, 0.61).v.bezier( 380 | east: (0.77, 0.64).v, 381 | west: (0.58, 0.59).v, 382 | ), 383 | (0.93, 0.6).v.bezier( 384 | north: (0.93, 0.53).v, 385 | ), 386 | (1.03, 0.7).v, 387 | (-0.03, 1.04).v, 388 | (0.35, 1.02).v, 389 | (0.65, 1.06).v, 390 | (0.93, 1.02).v, 391 | (1.07, 1.03).v, 392 | ], 393 | colors: const [ 394 | Color(0xffade3fa), 395 | Color(0xffade3fa), 396 | Color(0xffade3fa), 397 | Color(0xffade3fa), 398 | Color(0xffade3fa), 399 | Color(0xffeef8ff), 400 | Color(0xffd5ebff), 401 | Color(0xffedf5ff), 402 | Color(0xffedf5ff), 403 | Color(0xffedf5ff), 404 | Color(0x73efe8ff), 405 | Color(0xffe8f8ff), 406 | Color(0xffe8f8ff), 407 | Color(0xffe8f8ff), 408 | Color(0xffe8f8ff), 409 | Color(0xff003ebc), 410 | Color(0xff4f00f5), 411 | Color(0xff004ac8), 412 | Color(0xff003ebc), 413 | Color(0xff005495), 414 | Color(0xf7020224), 415 | Color(0xf7020224), 416 | Color(0xf7020224), 417 | Color(0xf7020224), 418 | Color(0xf7020224), 419 | ], 420 | ); 421 | -------------------------------------------------------------------------------- /lib/src/glass.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025, Renan Araujo 2 | // Use of this source code is governed by a MPL-2.0 license that can be 3 | // found in the LICENSE file. 4 | 5 | import 'dart:ui' as ui; 6 | 7 | import 'package:flutter/widgets.dart'; 8 | import 'package:flutter_shaders/flutter_shaders.dart'; 9 | import 'package:liquido/src/glass_options.dart'; 10 | import 'package:liquido/src/glass_render_object_widget.dart'; 11 | 12 | const String _kGlassShaderAssetKey = 13 | 'packages/liquido/shaders/liquido_impeller.frag'; 14 | 15 | /// A widget that creates a glass effect. 16 | /// 17 | /// See the construtors for different ways to create a glass effect: 18 | /// [Glass], [Glass.text], and [Glass.custom]. 19 | /// 20 | /// See also: 21 | /// - [GlassOptionsInterface] for the parameters that can be used to customize 22 | /// the glass effect. 23 | /// - [GlassOptions.defaultOptions], the default values for these 24 | /// parameters. 25 | abstract class Glass implements Widget, GlassOptionsInterface { 26 | /// Creates a glass effect container. 27 | /// 28 | /// If [child] is not provided, the behavior is similar to a 29 | /// [Container]. 30 | /// 31 | /// For [shape], if not provided, a rounded superellipse border 32 | /// with a radius of 30 is used. 33 | /// 34 | /// Any subclass of [ShapeBorder] can be used here, but it is recommended 35 | /// to use a shape that is compatible with the glass effect, such as: 36 | /// - [RoundedSuperellipseBorder] 37 | /// - [RoundedRectangleBorder] 38 | /// - [CircleBorder] 39 | /// - [StadiumBorder] 40 | /// - [BeveledRectangleBorder] 41 | /// - [ContinuousRectangleBorder] 42 | /// 43 | /// If not provided, the default [shape] is a 44 | /// [RoundedSuperellipseBorder] with a radius of 30. 45 | /// 46 | /// Glass effect parameters: 47 | /// {@macro glass_options} 48 | /// 49 | /// See also: 50 | /// - [GlassOptionsInterface] for the parameters that can be used to customize 51 | /// the glass effect. 52 | /// - [GlassOptions.defaultOptions], the default values for these 53 | /// parameters. 54 | /// - [Glass.text] for a convenience constructor that creates a glass effect 55 | /// with a [Text] widget. 56 | /// - [Glass.custom] for a convenience constructor that creates a glass effect 57 | /// with a custom mask widget. 58 | const factory Glass({ 59 | Key? key, 60 | ShapeBorder? shape, 61 | 62 | // 63 | double? blurSigma, 64 | double? contrastBoost, 65 | double? saturationBoost, 66 | double? grainIntensity, 67 | double? brightnessCompensation, 68 | double? centerScale, 69 | double? edgeScale, 70 | Color? glassTint, 71 | double? refractionBorder, 72 | BoxShadow? boxShadow, 73 | Widget? child, 74 | }) = _GlassContainer; 75 | 76 | /// Creates a glass effect with a text widget. 77 | /// 78 | /// This constructor provides a convenient way to create text with a glass 79 | /// effect. It supports all standard text parameters from the [Text] widget 80 | /// for precise text styling and layout control. 81 | /// 82 | /// The text will automatically be used as both the glass shape mask and the 83 | /// content, creating a glass effect that perfectly follows the text shape. 84 | /// 85 | /// Example: 86 | /// ```dart 87 | /// Glass.text( 88 | /// 'Hello World', 89 | /// style: TextStyle(fontSize: 48, fontWeight: FontWeight.bold), 90 | /// blurSigma: 15.0, 91 | /// glassTint: Colors.blue.withOpacity(0.1), 92 | /// ) 93 | /// ``` 94 | /// 95 | /// Note: The text color, background color, decoration color, and shadows will 96 | /// be overridden to ensure compatibility with the glass effect. 97 | /// 98 | /// Glass effect parameters: 99 | /// {@macro glass_options} 100 | /// 101 | /// See also: 102 | /// - [Glass] for a container with a glass effect. 103 | /// - [Glass.custom] for a glass effect with a custom mask. 104 | /// - [GlassOptionsInterface] for details about the glass effect parameters. 105 | const factory Glass.text( 106 | String data, { 107 | Key? key, 108 | TextStyle? style, 109 | StrutStyle? strutStyle, 110 | TextAlign? textAlign, 111 | TextDirection? textDirection, 112 | Locale? locale, 113 | bool? softWrap, 114 | TextOverflow? overflow, 115 | TextScaler? textScaler, 116 | int? maxLines, 117 | String? semanticsLabel, 118 | String? semanticsIdentifier, 119 | TextWidthBasis? textWidthBasis, 120 | ui.TextHeightBehavior? textHeightBehavior, 121 | 122 | // 123 | double? blurSigma, 124 | double? contrastBoost, 125 | double? saturationBoost, 126 | double? grainIntensity, 127 | double? brightnessCompensation, 128 | double? centerScale, 129 | double? edgeScale, 130 | Color? glassTint, 131 | double? refractionBorder, 132 | BoxShadow? boxShadow, 133 | }) = _GlassText; 134 | 135 | /// Creates a glass effect with a custom mask widget. 136 | /// 137 | /// This constructor provides the most flexibility by allowing you to specify 138 | /// both the mask widget that defines the shape of the glass effect and the 139 | /// child widget that will be displayed through the glass. 140 | /// 141 | /// The [mask] widget should use solid colors (nothing but 1.0 or 0.0 in the 142 | /// alpha channel) for areas where the 143 | /// glass effect should be applied, and transparent for areas where it should 144 | /// not. 145 | /// 146 | /// ⚠️ Warning! Dont use shadows, blurs or any other shenanigan thay may 147 | /// result in semi transparent pixels in the mask widget, as this may lead to 148 | /// unexpected results. The mask should be a solid shape with no transparency. 149 | /// 150 | /// This allows for complex glass effects like custom-shaped windows, cutouts, 151 | /// or applying glass effects to specific regions of a widget. 152 | /// 153 | /// Glass effect parameters: 154 | /// {@macro glass_options} 155 | /// 156 | /// See also: 157 | /// - [Glass] for a container with a glass effect. 158 | /// - [Glass.text] for a glass effect with text. 159 | /// - [GlassOptionsInterface] for details about the glass effect parameters. 160 | const factory Glass.custom({ 161 | required Widget mask, 162 | required Widget child, 163 | Key? key, 164 | 165 | // 166 | double? blurSigma, 167 | double? contrastBoost, 168 | double? saturationBoost, 169 | double? grainIntensity, 170 | double? brightnessCompensation, 171 | double? centerScale, 172 | double? edgeScale, 173 | Color? glassTint, 174 | double? refractionBorder, 175 | BoxShadow? boxShadow, 176 | }) = _CustomGlass; 177 | } 178 | 179 | class _GlassContainer extends StatelessWidget implements Glass { 180 | const _GlassContainer({ 181 | super.key, 182 | this.shape, 183 | this.child, 184 | this.blurSigma, 185 | this.contrastBoost, 186 | this.saturationBoost, 187 | this.grainIntensity, 188 | this.brightnessCompensation, 189 | this.centerScale, 190 | this.edgeScale, 191 | this.glassTint, 192 | this.refractionBorder, 193 | this.boxShadow, 194 | }); 195 | 196 | final Widget? child; 197 | final ShapeBorder? shape; 198 | 199 | @override 200 | final double? blurSigma; 201 | @override 202 | final BoxShadow? boxShadow; 203 | @override 204 | final double? brightnessCompensation; 205 | @override 206 | final double? centerScale; 207 | @override 208 | final double? contrastBoost; 209 | @override 210 | final double? edgeScale; 211 | @override 212 | final ui.Color? glassTint; 213 | @override 214 | final double? grainIntensity; 215 | @override 216 | final double? refractionBorder; 217 | @override 218 | final double? saturationBoost; 219 | 220 | @override 221 | Widget build(BuildContext context) { 222 | return _Glass( 223 | options: this, 224 | mask: DecoratedBox( 225 | decoration: ShapeDecoration( 226 | color: const Color(0xFF000000), 227 | shape: 228 | shape ?? 229 | RoundedSuperellipseBorder( 230 | borderRadius: BorderRadius.circular(30), 231 | ), 232 | ), 233 | ), 234 | child: Container(child: child), 235 | ); 236 | } 237 | } 238 | 239 | class _GlassText extends StatelessWidget implements Glass { 240 | const _GlassText( 241 | this.data, { 242 | super.key, 243 | this.style, 244 | this.strutStyle, 245 | this.textAlign, 246 | this.textDirection, 247 | this.locale, 248 | this.softWrap, 249 | this.overflow, 250 | this.textScaler, 251 | this.maxLines, 252 | this.semanticsLabel, 253 | this.semanticsIdentifier, 254 | this.textWidthBasis, 255 | this.textHeightBehavior, 256 | 257 | this.blurSigma, 258 | this.contrastBoost, 259 | this.saturationBoost, 260 | this.grainIntensity, 261 | this.brightnessCompensation, 262 | this.centerScale, 263 | this.edgeScale, 264 | this.glassTint, 265 | this.refractionBorder, 266 | this.boxShadow, 267 | }); 268 | 269 | @override 270 | final double? blurSigma; 271 | @override 272 | final BoxShadow? boxShadow; 273 | @override 274 | final double? brightnessCompensation; 275 | @override 276 | final double? centerScale; 277 | @override 278 | final double? contrastBoost; 279 | @override 280 | final double? edgeScale; 281 | @override 282 | final ui.Color? glassTint; 283 | @override 284 | final double? grainIntensity; 285 | @override 286 | final double? refractionBorder; 287 | @override 288 | final double? saturationBoost; 289 | 290 | final String data; 291 | 292 | final TextStyle? style; 293 | 294 | final StrutStyle? strutStyle; 295 | 296 | final TextAlign? textAlign; 297 | 298 | final TextDirection? textDirection; 299 | 300 | final Locale? locale; 301 | 302 | final bool? softWrap; 303 | 304 | final TextOverflow? overflow; 305 | 306 | final TextScaler? textScaler; 307 | 308 | final int? maxLines; 309 | 310 | final String? semanticsLabel; 311 | 312 | final String? semanticsIdentifier; 313 | 314 | final TextWidthBasis? textWidthBasis; 315 | 316 | final ui.TextHeightBehavior? textHeightBehavior; 317 | 318 | @override 319 | Widget build(BuildContext context) { 320 | final defaultTextStyle = DefaultTextStyle.of(context); 321 | var effectiveTextStyle = style; 322 | if (style == null || style!.inherit) { 323 | effectiveTextStyle = defaultTextStyle.style.merge(style); 324 | } 325 | if (MediaQuery.boldTextOf(context)) { 326 | effectiveTextStyle = effectiveTextStyle!.merge( 327 | const TextStyle(fontWeight: FontWeight.bold), 328 | ); 329 | } 330 | 331 | // Prevent user from doing anything stupid 332 | effectiveTextStyle = effectiveTextStyle!.copyWith( 333 | color: const Color(0xFF000000), 334 | decorationColor: const Color(0x00000000), 335 | backgroundColor: const Color(0x00000000), 336 | shadows: [], 337 | ); 338 | 339 | final text = Text( 340 | data, 341 | style: effectiveTextStyle, 342 | // strutStyle: strutStyle, 343 | textAlign: textAlign, 344 | textDirection: textDirection, 345 | locale: locale, 346 | softWrap: softWrap, 347 | overflow: overflow, 348 | textScaler: textScaler, 349 | maxLines: maxLines, 350 | semanticsLabel: semanticsLabel, 351 | semanticsIdentifier: semanticsIdentifier, 352 | textWidthBasis: textWidthBasis, 353 | textHeightBehavior: textHeightBehavior, 354 | selectionColor: const Color(0x00000000), 355 | ); 356 | 357 | return _Glass( 358 | options: this, 359 | mask: text, 360 | child: Opacity( 361 | opacity: 0, 362 | child: text, 363 | ), 364 | ); 365 | } 366 | } 367 | 368 | class _CustomGlass extends StatelessWidget implements Glass { 369 | const _CustomGlass({ 370 | required this.mask, 371 | required this.child, 372 | super.key, 373 | this.blurSigma, 374 | this.contrastBoost, 375 | this.saturationBoost, 376 | this.grainIntensity, 377 | this.brightnessCompensation, 378 | this.centerScale, 379 | this.edgeScale, 380 | this.glassTint, 381 | this.refractionBorder, 382 | this.boxShadow, 383 | }); 384 | 385 | @override 386 | final double? blurSigma; 387 | @override 388 | final BoxShadow? boxShadow; 389 | @override 390 | final double? brightnessCompensation; 391 | @override 392 | final double? centerScale; 393 | @override 394 | final double? contrastBoost; 395 | @override 396 | final double? edgeScale; 397 | @override 398 | final ui.Color? glassTint; 399 | @override 400 | final double? grainIntensity; 401 | @override 402 | final double? refractionBorder; 403 | @override 404 | final double? saturationBoost; 405 | 406 | final Widget child; 407 | final Widget mask; 408 | 409 | @override 410 | Widget build(BuildContext context) { 411 | return _Glass( 412 | options: this, 413 | mask: mask, 414 | child: child, 415 | ); 416 | } 417 | } 418 | 419 | class _Glass extends StatelessWidget { 420 | const _Glass({ 421 | required this.options, 422 | required this.child, 423 | required this.mask, 424 | }); 425 | 426 | final GlassOptionsInterface options; 427 | final Widget child; 428 | final Widget mask; 429 | 430 | @override 431 | Widget build(BuildContext context) { 432 | return ShaderBuilder( 433 | assetKey: _kGlassShaderAssetKey, 434 | (context, shader, childWidget) { 435 | return GlassRenderObjectWidget( 436 | shader: shader, 437 | pixelRatio: MediaQuery.of(context).devicePixelRatio, 438 | mask: mask, 439 | options: GlassOptions.fromInterface(options), 440 | child: childWidget!, 441 | ); 442 | }, 443 | child: child, 444 | ); 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025, Renan Araujo 2 | 3 | Mozilla Public License Version 2.0 4 | ================================== 5 | 6 | 1. Definitions 7 | -------------- 8 | 9 | 1.1. "Contributor" 10 | means each individual or legal entity that creates, contributes to 11 | the creation of, or owns Covered Software. 12 | 13 | 1.2. "Contributor Version" 14 | means the combination of the Contributions of others (if any) used 15 | by a Contributor and that particular Contributor's Contribution. 16 | 17 | 1.3. "Contribution" 18 | means Covered Software of a particular Contributor. 19 | 20 | 1.4. "Covered Software" 21 | means Source Code Form to which the initial Contributor has attached 22 | the notice in Exhibit A, the Executable Form of such Source Code 23 | Form, and Modifications of such Source Code Form, in each case 24 | including portions thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | (a) that the initial Contributor has attached the notice described 30 | in Exhibit B to the Covered Software; or 31 | 32 | (b) that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the 34 | terms of a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | means any form of the work other than Source Code Form. 38 | 39 | 1.7. "Larger Work" 40 | means a work that combines Covered Software with other material, in 41 | a separate file or files, that is not Covered Software. 42 | 43 | 1.8. "License" 44 | means this document. 45 | 46 | 1.9. "Licensable" 47 | means having the right to grant, to the maximum extent possible, 48 | whether at the time of the initial grant or subsequently, any and 49 | all of the rights conveyed by this License. 50 | 51 | 1.10. "Modifications" 52 | means any of the following: 53 | 54 | (a) any file in Source Code Form that results from an addition to, 55 | deletion from, or modification of the contents of Covered 56 | Software; or 57 | 58 | (b) any new file in Source Code Form that contains any Covered 59 | Software. 60 | 61 | 1.11. "Patent Claims" of a Contributor 62 | means any patent claim(s), including without limitation, method, 63 | process, and apparatus claims, in any patent Licensable by such 64 | Contributor that would be infringed, but for the grant of the 65 | License, by the making, using, selling, offering for sale, having 66 | made, import, or transfer of either its Contributions or its 67 | Contributor Version. 68 | 69 | 1.12. "Secondary License" 70 | means either the GNU General Public License, Version 2.0, the GNU 71 | Lesser General Public License, Version 2.1, the GNU Affero General 72 | Public License, Version 3.0, or any later versions of those 73 | licenses. 74 | 75 | 1.13. "Source Code Form" 76 | means the form of the work preferred for making modifications. 77 | 78 | 1.14. "You" (or "Your") 79 | means an individual or a legal entity exercising rights under this 80 | License. For legal entities, "You" includes any entity that 81 | controls, is controlled by, or is under common control with You. For 82 | purposes of this definition, "control" means (a) the power, direct 83 | or indirect, to cause the direction or management of such entity, 84 | whether by contract or otherwise, or (b) ownership of more than 85 | fifty percent (50%) of the outstanding shares or beneficial 86 | ownership of such entity. 87 | 88 | 2. License Grants and Conditions 89 | -------------------------------- 90 | 91 | 2.1. Grants 92 | 93 | Each Contributor hereby grants You a world-wide, royalty-free, 94 | non-exclusive license: 95 | 96 | (a) under intellectual property rights (other than patent or trademark) 97 | Licensable by such Contributor to use, reproduce, make available, 98 | modify, display, perform, distribute, and otherwise exploit its 99 | Contributions, either on an unmodified basis, with Modifications, or 100 | as part of a Larger Work; and 101 | 102 | (b) under Patent Claims of such Contributor to make, use, sell, offer 103 | for sale, have made, import, and otherwise transfer either its 104 | Contributions or its Contributor Version. 105 | 106 | 2.2. Effective Date 107 | 108 | The licenses granted in Section 2.1 with respect to any Contribution 109 | become effective for each Contribution on the date the Contributor first 110 | distributes such Contribution. 111 | 112 | 2.3. Limitations on Grant Scope 113 | 114 | The licenses granted in this Section 2 are the only rights granted under 115 | this License. No additional rights or licenses will be implied from the 116 | distribution or licensing of Covered Software under this License. 117 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 118 | Contributor: 119 | 120 | (a) for any code that a Contributor has removed from Covered Software; 121 | or 122 | 123 | (b) for infringements caused by: (i) Your and any other third party's 124 | modifications of Covered Software, or (ii) the combination of its 125 | Contributions with other software (except as part of its Contributor 126 | Version); or 127 | 128 | (c) under Patent Claims infringed by Covered Software in the absence of 129 | its Contributions. 130 | 131 | This License does not grant any rights in the trademarks, service marks, 132 | or logos of any Contributor (except as may be necessary to comply with 133 | the notice requirements in Section 3.4). 134 | 135 | 2.4. Subsequent Licenses 136 | 137 | No Contributor makes additional grants as a result of Your choice to 138 | distribute the Covered Software under a subsequent version of this 139 | License (see Section 10.2) or under the terms of a Secondary License (if 140 | permitted under the terms of Section 3.3). 141 | 142 | 2.5. Representation 143 | 144 | Each Contributor represents that the Contributor believes its 145 | Contributions are its original creation(s) or it has sufficient rights 146 | to grant the rights to its Contributions conveyed by this License. 147 | 148 | 2.6. Fair Use 149 | 150 | This License is not intended to limit any rights You have under 151 | applicable copyright doctrines of fair use, fair dealing, or other 152 | equivalents. 153 | 154 | 2.7. Conditions 155 | 156 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 157 | in Section 2.1. 158 | 159 | 3. Responsibilities 160 | ------------------- 161 | 162 | 3.1. Distribution of Source Form 163 | 164 | All distribution of Covered Software in Source Code Form, including any 165 | Modifications that You create or to which You contribute, must be under 166 | the terms of this License. You must inform recipients that the Source 167 | Code Form of the Covered Software is governed by the terms of this 168 | License, and how they can obtain a copy of this License. You may not 169 | attempt to alter or restrict the recipients' rights in the Source Code 170 | Form. 171 | 172 | 3.2. Distribution of Executable Form 173 | 174 | If You distribute Covered Software in Executable Form then: 175 | 176 | (a) such Covered Software must also be made available in Source Code 177 | Form, as described in Section 3.1, and You must inform recipients of 178 | the Executable Form how they can obtain a copy of such Source Code 179 | Form by reasonable means in a timely manner, at a charge no more 180 | than the cost of distribution to the recipient; and 181 | 182 | (b) You may distribute such Executable Form under the terms of this 183 | License, or sublicense it under different terms, provided that the 184 | license for the Executable Form does not attempt to limit or alter 185 | the recipients' rights in the Source Code Form under this License. 186 | 187 | 3.3. Distribution of a Larger Work 188 | 189 | You may create and distribute a Larger Work under terms of Your choice, 190 | provided that You also comply with the requirements of this License for 191 | the Covered Software. If the Larger Work is a combination of Covered 192 | Software with a work governed by one or more Secondary Licenses, and the 193 | Covered Software is not Incompatible With Secondary Licenses, this 194 | License permits You to additionally distribute such Covered Software 195 | under the terms of such Secondary License(s), so that the recipient of 196 | the Larger Work may, at their option, further distribute the Covered 197 | Software under the terms of either this License or such Secondary 198 | License(s). 199 | 200 | 3.4. Notices 201 | 202 | You may not remove or alter the substance of any license notices 203 | (including copyright notices, patent notices, disclaimers of warranty, 204 | or limitations of liability) contained within the Source Code Form of 205 | the Covered Software, except that You may alter any license notices to 206 | the extent required to remedy known factual inaccuracies. 207 | 208 | 3.5. Application of Additional Terms 209 | 210 | You may choose to offer, and to charge a fee for, warranty, support, 211 | indemnity or liability obligations to one or more recipients of Covered 212 | Software. However, You may do so only on Your own behalf, and not on 213 | behalf of any Contributor. You must make it absolutely clear that any 214 | such warranty, support, indemnity, or liability obligation is offered by 215 | You alone, and You hereby agree to indemnify every Contributor for any 216 | liability incurred by such Contributor as a result of warranty, support, 217 | indemnity or liability terms You offer. You may include additional 218 | disclaimers of warranty and limitations of liability specific to any 219 | jurisdiction. 220 | 221 | 4. Inability to Comply Due to Statute or Regulation 222 | --------------------------------------------------- 223 | 224 | If it is impossible for You to comply with any of the terms of this 225 | License with respect to some or all of the Covered Software due to 226 | statute, judicial order, or regulation then You must: (a) comply with 227 | the terms of this License to the maximum extent possible; and (b) 228 | describe the limitations and the code they affect. Such description must 229 | be placed in a text file included with all distributions of the Covered 230 | Software under this License. Except to the extent prohibited by statute 231 | or regulation, such description must be sufficiently detailed for a 232 | recipient of ordinary skill to be able to understand it. 233 | 234 | 5. Termination 235 | -------------- 236 | 237 | 5.1. The rights granted under this License will terminate automatically 238 | if You fail to comply with any of its terms. However, if You become 239 | compliant, then the rights granted under this License from a particular 240 | Contributor are reinstated (a) provisionally, unless and until such 241 | Contributor explicitly and finally terminates Your grants, and (b) on an 242 | ongoing basis, if such Contributor fails to notify You of the 243 | non-compliance by some reasonable means prior to 60 days after You have 244 | come back into compliance. Moreover, Your grants from a particular 245 | Contributor are reinstated on an ongoing basis if such Contributor 246 | notifies You of the non-compliance by some reasonable means, this is the 247 | first time You have received notice of non-compliance with this License 248 | from such Contributor, and You become compliant prior to 30 days after 249 | Your receipt of the notice. 250 | 251 | 5.2. If You initiate litigation against any entity by asserting a patent 252 | infringement claim (excluding declaratory judgment actions, 253 | counter-claims, and cross-claims) alleging that a Contributor Version 254 | directly or indirectly infringes any patent, then the rights granted to 255 | You by any and all Contributors for the Covered Software under Section 256 | 2.1 of this License shall terminate. 257 | 258 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 259 | end user license agreements (excluding distributors and resellers) which 260 | have been validly granted by You or Your distributors under this License 261 | prior to termination shall survive termination. 262 | 263 | ************************************************************************ 264 | * * 265 | * 6. Disclaimer of Warranty * 266 | * ------------------------- * 267 | * * 268 | * Covered Software is provided under this License on an "as is" * 269 | * basis, without warranty of any kind, either expressed, implied, or * 270 | * statutory, including, without limitation, warranties that the * 271 | * Covered Software is free of defects, merchantable, fit for a * 272 | * particular purpose or non-infringing. The entire risk as to the * 273 | * quality and performance of the Covered Software is with You. * 274 | * Should any Covered Software prove defective in any respect, You * 275 | * (not any Contributor) assume the cost of any necessary servicing, * 276 | * repair, or correction. This disclaimer of warranty constitutes an * 277 | * essential part of this License. No use of any Covered Software is * 278 | * authorized under this License except under this disclaimer. * 279 | * * 280 | ************************************************************************ 281 | 282 | ************************************************************************ 283 | * * 284 | * 7. Limitation of Liability * 285 | * -------------------------- * 286 | * * 287 | * Under no circumstances and under no legal theory, whether tort * 288 | * (including negligence), contract, or otherwise, shall any * 289 | * Contributor, or anyone who distributes Covered Software as * 290 | * permitted above, be liable to You for any direct, indirect, * 291 | * special, incidental, or consequential damages of any character * 292 | * including, without limitation, damages for lost profits, loss of * 293 | * goodwill, work stoppage, computer failure or malfunction, or any * 294 | * and all other commercial damages or losses, even if such party * 295 | * shall have been informed of the possibility of such damages. This * 296 | * limitation of liability shall not apply to liability for death or * 297 | * personal injury resulting from such party's negligence to the * 298 | * extent applicable law prohibits such limitation. Some * 299 | * jurisdictions do not allow the exclusion or limitation of * 300 | * incidental or consequential damages, so this exclusion and * 301 | * limitation may not apply to You. * 302 | * * 303 | ************************************************************************ 304 | 305 | 8. Litigation 306 | ------------- 307 | 308 | Any litigation relating to this License may be brought only in the 309 | courts of a jurisdiction where the defendant maintains its principal 310 | place of business and such litigation shall be governed by laws of that 311 | jurisdiction, without reference to its conflict-of-law provisions. 312 | Nothing in this Section shall prevent a party's ability to bring 313 | cross-claims or counter-claims. 314 | 315 | 9. Miscellaneous 316 | ---------------- 317 | 318 | This License represents the complete agreement concerning the subject 319 | matter hereof. If any provision of this License is held to be 320 | unenforceable, such provision shall be reformed only to the extent 321 | necessary to make it enforceable. Any law or regulation which provides 322 | that the language of a contract shall be construed against the drafter 323 | shall not be used to construe this License against a Contributor. 324 | 325 | 10. Versions of the License 326 | --------------------------- 327 | 328 | 10.1. New Versions 329 | 330 | Mozilla Foundation is the license steward. Except as provided in Section 331 | 10.3, no one other than the license steward has the right to modify or 332 | publish new versions of this License. Each version will be given a 333 | distinguishing version number. 334 | 335 | 10.2. Effect of New Versions 336 | 337 | You may distribute the Covered Software under the terms of the version 338 | of the License under which You originally received the Covered Software, 339 | or under the terms of any subsequent version published by the license 340 | steward. 341 | 342 | 10.3. Modified Versions 343 | 344 | If you create software not governed by this License, and you want to 345 | create a new license for such software, you may create and use a 346 | modified version of this License if you rename the license and remove 347 | any references to the name of the license steward (except to note that 348 | such modified license differs from this License). 349 | 350 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 351 | Licenses 352 | 353 | If You choose to distribute Source Code Form that is Incompatible With 354 | Secondary Licenses under the terms of this version of the License, the 355 | notice described in Exhibit B of this License must be attached. 356 | 357 | Exhibit A - Source Code Form License Notice 358 | ------------------------------------------- 359 | 360 | This Source Code Form is subject to the terms of the Mozilla Public 361 | License, v. 2.0. If a copy of the MPL was not distributed with this 362 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 363 | 364 | If it is not possible or desirable to put the notice in a particular 365 | file, then You may include the notice in a location (such as a LICENSE 366 | file in a relevant directory) where a recipient would be likely to look 367 | for such a notice. 368 | 369 | You may add additional accurate notices of copyright ownership. 370 | 371 | Exhibit B - "Incompatible With Secondary Licenses" Notice 372 | --------------------------------------------------------- 373 | 374 | This Source Code Form is "Incompatible With Secondary Licenses", as 375 | defined by the Mozilla Public License, v. 2.0. -------------------------------------------------------------------------------- /example/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025, Renan Araujo 2 | 3 | Mozilla Public License Version 2.0 4 | ================================== 5 | 6 | 1. Definitions 7 | -------------- 8 | 9 | 1.1. "Contributor" 10 | means each individual or legal entity that creates, contributes to 11 | the creation of, or owns Covered Software. 12 | 13 | 1.2. "Contributor Version" 14 | means the combination of the Contributions of others (if any) used 15 | by a Contributor and that particular Contributor's Contribution. 16 | 17 | 1.3. "Contribution" 18 | means Covered Software of a particular Contributor. 19 | 20 | 1.4. "Covered Software" 21 | means Source Code Form to which the initial Contributor has attached 22 | the notice in Exhibit A, the Executable Form of such Source Code 23 | Form, and Modifications of such Source Code Form, in each case 24 | including portions thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | (a) that the initial Contributor has attached the notice described 30 | in Exhibit B to the Covered Software; or 31 | 32 | (b) that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the 34 | terms of a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | means any form of the work other than Source Code Form. 38 | 39 | 1.7. "Larger Work" 40 | means a work that combines Covered Software with other material, in 41 | a separate file or files, that is not Covered Software. 42 | 43 | 1.8. "License" 44 | means this document. 45 | 46 | 1.9. "Licensable" 47 | means having the right to grant, to the maximum extent possible, 48 | whether at the time of the initial grant or subsequently, any and 49 | all of the rights conveyed by this License. 50 | 51 | 1.10. "Modifications" 52 | means any of the following: 53 | 54 | (a) any file in Source Code Form that results from an addition to, 55 | deletion from, or modification of the contents of Covered 56 | Software; or 57 | 58 | (b) any new file in Source Code Form that contains any Covered 59 | Software. 60 | 61 | 1.11. "Patent Claims" of a Contributor 62 | means any patent claim(s), including without limitation, method, 63 | process, and apparatus claims, in any patent Licensable by such 64 | Contributor that would be infringed, but for the grant of the 65 | License, by the making, using, selling, offering for sale, having 66 | made, import, or transfer of either its Contributions or its 67 | Contributor Version. 68 | 69 | 1.12. "Secondary License" 70 | means either the GNU General Public License, Version 2.0, the GNU 71 | Lesser General Public License, Version 2.1, the GNU Affero General 72 | Public License, Version 3.0, or any later versions of those 73 | licenses. 74 | 75 | 1.13. "Source Code Form" 76 | means the form of the work preferred for making modifications. 77 | 78 | 1.14. "You" (or "Your") 79 | means an individual or a legal entity exercising rights under this 80 | License. For legal entities, "You" includes any entity that 81 | controls, is controlled by, or is under common control with You. For 82 | purposes of this definition, "control" means (a) the power, direct 83 | or indirect, to cause the direction or management of such entity, 84 | whether by contract or otherwise, or (b) ownership of more than 85 | fifty percent (50%) of the outstanding shares or beneficial 86 | ownership of such entity. 87 | 88 | 2. License Grants and Conditions 89 | -------------------------------- 90 | 91 | 2.1. Grants 92 | 93 | Each Contributor hereby grants You a world-wide, royalty-free, 94 | non-exclusive license: 95 | 96 | (a) under intellectual property rights (other than patent or trademark) 97 | Licensable by such Contributor to use, reproduce, make available, 98 | modify, display, perform, distribute, and otherwise exploit its 99 | Contributions, either on an unmodified basis, with Modifications, or 100 | as part of a Larger Work; and 101 | 102 | (b) under Patent Claims of such Contributor to make, use, sell, offer 103 | for sale, have made, import, and otherwise transfer either its 104 | Contributions or its Contributor Version. 105 | 106 | 2.2. Effective Date 107 | 108 | The licenses granted in Section 2.1 with respect to any Contribution 109 | become effective for each Contribution on the date the Contributor first 110 | distributes such Contribution. 111 | 112 | 2.3. Limitations on Grant Scope 113 | 114 | The licenses granted in this Section 2 are the only rights granted under 115 | this License. No additional rights or licenses will be implied from the 116 | distribution or licensing of Covered Software under this License. 117 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 118 | Contributor: 119 | 120 | (a) for any code that a Contributor has removed from Covered Software; 121 | or 122 | 123 | (b) for infringements caused by: (i) Your and any other third party's 124 | modifications of Covered Software, or (ii) the combination of its 125 | Contributions with other software (except as part of its Contributor 126 | Version); or 127 | 128 | (c) under Patent Claims infringed by Covered Software in the absence of 129 | its Contributions. 130 | 131 | This License does not grant any rights in the trademarks, service marks, 132 | or logos of any Contributor (except as may be necessary to comply with 133 | the notice requirements in Section 3.4). 134 | 135 | 2.4. Subsequent Licenses 136 | 137 | No Contributor makes additional grants as a result of Your choice to 138 | distribute the Covered Software under a subsequent version of this 139 | License (see Section 10.2) or under the terms of a Secondary License (if 140 | permitted under the terms of Section 3.3). 141 | 142 | 2.5. Representation 143 | 144 | Each Contributor represents that the Contributor believes its 145 | Contributions are its original creation(s) or it has sufficient rights 146 | to grant the rights to its Contributions conveyed by this License. 147 | 148 | 2.6. Fair Use 149 | 150 | This License is not intended to limit any rights You have under 151 | applicable copyright doctrines of fair use, fair dealing, or other 152 | equivalents. 153 | 154 | 2.7. Conditions 155 | 156 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 157 | in Section 2.1. 158 | 159 | 3. Responsibilities 160 | ------------------- 161 | 162 | 3.1. Distribution of Source Form 163 | 164 | All distribution of Covered Software in Source Code Form, including any 165 | Modifications that You create or to which You contribute, must be under 166 | the terms of this License. You must inform recipients that the Source 167 | Code Form of the Covered Software is governed by the terms of this 168 | License, and how they can obtain a copy of this License. You may not 169 | attempt to alter or restrict the recipients' rights in the Source Code 170 | Form. 171 | 172 | 3.2. Distribution of Executable Form 173 | 174 | If You distribute Covered Software in Executable Form then: 175 | 176 | (a) such Covered Software must also be made available in Source Code 177 | Form, as described in Section 3.1, and You must inform recipients of 178 | the Executable Form how they can obtain a copy of such Source Code 179 | Form by reasonable means in a timely manner, at a charge no more 180 | than the cost of distribution to the recipient; and 181 | 182 | (b) You may distribute such Executable Form under the terms of this 183 | License, or sublicense it under different terms, provided that the 184 | license for the Executable Form does not attempt to limit or alter 185 | the recipients' rights in the Source Code Form under this License. 186 | 187 | 3.3. Distribution of a Larger Work 188 | 189 | You may create and distribute a Larger Work under terms of Your choice, 190 | provided that You also comply with the requirements of this License for 191 | the Covered Software. If the Larger Work is a combination of Covered 192 | Software with a work governed by one or more Secondary Licenses, and the 193 | Covered Software is not Incompatible With Secondary Licenses, this 194 | License permits You to additionally distribute such Covered Software 195 | under the terms of such Secondary License(s), so that the recipient of 196 | the Larger Work may, at their option, further distribute the Covered 197 | Software under the terms of either this License or such Secondary 198 | License(s). 199 | 200 | 3.4. Notices 201 | 202 | You may not remove or alter the substance of any license notices 203 | (including copyright notices, patent notices, disclaimers of warranty, 204 | or limitations of liability) contained within the Source Code Form of 205 | the Covered Software, except that You may alter any license notices to 206 | the extent required to remedy known factual inaccuracies. 207 | 208 | 3.5. Application of Additional Terms 209 | 210 | You may choose to offer, and to charge a fee for, warranty, support, 211 | indemnity or liability obligations to one or more recipients of Covered 212 | Software. However, You may do so only on Your own behalf, and not on 213 | behalf of any Contributor. You must make it absolutely clear that any 214 | such warranty, support, indemnity, or liability obligation is offered by 215 | You alone, and You hereby agree to indemnify every Contributor for any 216 | liability incurred by such Contributor as a result of warranty, support, 217 | indemnity or liability terms You offer. You may include additional 218 | disclaimers of warranty and limitations of liability specific to any 219 | jurisdiction. 220 | 221 | 4. Inability to Comply Due to Statute or Regulation 222 | --------------------------------------------------- 223 | 224 | If it is impossible for You to comply with any of the terms of this 225 | License with respect to some or all of the Covered Software due to 226 | statute, judicial order, or regulation then You must: (a) comply with 227 | the terms of this License to the maximum extent possible; and (b) 228 | describe the limitations and the code they affect. Such description must 229 | be placed in a text file included with all distributions of the Covered 230 | Software under this License. Except to the extent prohibited by statute 231 | or regulation, such description must be sufficiently detailed for a 232 | recipient of ordinary skill to be able to understand it. 233 | 234 | 5. Termination 235 | -------------- 236 | 237 | 5.1. The rights granted under this License will terminate automatically 238 | if You fail to comply with any of its terms. However, if You become 239 | compliant, then the rights granted under this License from a particular 240 | Contributor are reinstated (a) provisionally, unless and until such 241 | Contributor explicitly and finally terminates Your grants, and (b) on an 242 | ongoing basis, if such Contributor fails to notify You of the 243 | non-compliance by some reasonable means prior to 60 days after You have 244 | come back into compliance. Moreover, Your grants from a particular 245 | Contributor are reinstated on an ongoing basis if such Contributor 246 | notifies You of the non-compliance by some reasonable means, this is the 247 | first time You have received notice of non-compliance with this License 248 | from such Contributor, and You become compliant prior to 30 days after 249 | Your receipt of the notice. 250 | 251 | 5.2. If You initiate litigation against any entity by asserting a patent 252 | infringement claim (excluding declaratory judgment actions, 253 | counter-claims, and cross-claims) alleging that a Contributor Version 254 | directly or indirectly infringes any patent, then the rights granted to 255 | You by any and all Contributors for the Covered Software under Section 256 | 2.1 of this License shall terminate. 257 | 258 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 259 | end user license agreements (excluding distributors and resellers) which 260 | have been validly granted by You or Your distributors under this License 261 | prior to termination shall survive termination. 262 | 263 | ************************************************************************ 264 | * * 265 | * 6. Disclaimer of Warranty * 266 | * ------------------------- * 267 | * * 268 | * Covered Software is provided under this License on an "as is" * 269 | * basis, without warranty of any kind, either expressed, implied, or * 270 | * statutory, including, without limitation, warranties that the * 271 | * Covered Software is free of defects, merchantable, fit for a * 272 | * particular purpose or non-infringing. The entire risk as to the * 273 | * quality and performance of the Covered Software is with You. * 274 | * Should any Covered Software prove defective in any respect, You * 275 | * (not any Contributor) assume the cost of any necessary servicing, * 276 | * repair, or correction. This disclaimer of warranty constitutes an * 277 | * essential part of this License. No use of any Covered Software is * 278 | * authorized under this License except under this disclaimer. * 279 | * * 280 | ************************************************************************ 281 | 282 | ************************************************************************ 283 | * * 284 | * 7. Limitation of Liability * 285 | * -------------------------- * 286 | * * 287 | * Under no circumstances and under no legal theory, whether tort * 288 | * (including negligence), contract, or otherwise, shall any * 289 | * Contributor, or anyone who distributes Covered Software as * 290 | * permitted above, be liable to You for any direct, indirect, * 291 | * special, incidental, or consequential damages of any character * 292 | * including, without limitation, damages for lost profits, loss of * 293 | * goodwill, work stoppage, computer failure or malfunction, or any * 294 | * and all other commercial damages or losses, even if such party * 295 | * shall have been informed of the possibility of such damages. This * 296 | * limitation of liability shall not apply to liability for death or * 297 | * personal injury resulting from such party's negligence to the * 298 | * extent applicable law prohibits such limitation. Some * 299 | * jurisdictions do not allow the exclusion or limitation of * 300 | * incidental or consequential damages, so this exclusion and * 301 | * limitation may not apply to You. * 302 | * * 303 | ************************************************************************ 304 | 305 | 8. Litigation 306 | ------------- 307 | 308 | Any litigation relating to this License may be brought only in the 309 | courts of a jurisdiction where the defendant maintains its principal 310 | place of business and such litigation shall be governed by laws of that 311 | jurisdiction, without reference to its conflict-of-law provisions. 312 | Nothing in this Section shall prevent a party's ability to bring 313 | cross-claims or counter-claims. 314 | 315 | 9. Miscellaneous 316 | ---------------- 317 | 318 | This License represents the complete agreement concerning the subject 319 | matter hereof. If any provision of this License is held to be 320 | unenforceable, such provision shall be reformed only to the extent 321 | necessary to make it enforceable. Any law or regulation which provides 322 | that the language of a contract shall be construed against the drafter 323 | shall not be used to construe this License against a Contributor. 324 | 325 | 10. Versions of the License 326 | --------------------------- 327 | 328 | 10.1. New Versions 329 | 330 | Mozilla Foundation is the license steward. Except as provided in Section 331 | 10.3, no one other than the license steward has the right to modify or 332 | publish new versions of this License. Each version will be given a 333 | distinguishing version number. 334 | 335 | 10.2. Effect of New Versions 336 | 337 | You may distribute the Covered Software under the terms of the version 338 | of the License under which You originally received the Covered Software, 339 | or under the terms of any subsequent version published by the license 340 | steward. 341 | 342 | 10.3. Modified Versions 343 | 344 | If you create software not governed by this License, and you want to 345 | create a new license for such software, you may create and use a 346 | modified version of this License if you rename the license and remove 347 | any references to the name of the license steward (except to note that 348 | such modified license differs from this License). 349 | 350 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 351 | Licenses 352 | 353 | If You choose to distribute Source Code Form that is Incompatible With 354 | Secondary Licenses under the terms of this version of the License, the 355 | notice described in Exhibit B of this License must be attached. 356 | 357 | Exhibit A - Source Code Form License Notice 358 | ------------------------------------------- 359 | 360 | This Source Code Form is subject to the terms of the Mozilla Public 361 | License, v. 2.0. If a copy of the MPL was not distributed with this 362 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 363 | 364 | If it is not possible or desirable to put the notice in a particular 365 | file, then You may include the notice in a location (such as a LICENSE 366 | file in a relevant directory) where a recipient would be likely to look 367 | for such a notice. 368 | 369 | You may add additional accurate notices of copyright ownership. 370 | 371 | Exhibit B - "Incompatible With Secondary Licenses" Notice 372 | --------------------------------------------------------- 373 | 374 | This Source Code Form is "Incompatible With Secondary Licenses", as 375 | defined by the Mozilla Public License, v. 2.0. -------------------------------------------------------------------------------- /lib/src/glass_render_object_widget.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025, Renan Araujo 2 | // Use of this source code is governed by a MPL-2.0 license that can be 3 | // found in the LICENSE file. 4 | 5 | // This file is mostly internal, I am not documenting this. 6 | // ignore_for_file: public_member_api_docs 7 | 8 | import 'dart:ui' as ui; 9 | 10 | import 'package:flutter/rendering.dart'; 11 | import 'package:flutter/widgets.dart'; 12 | import 'package:flutter_shaders/flutter_shaders.dart'; 13 | import 'package:liquido/src/glass.dart'; 14 | 15 | import 'package:liquido/src/glass_options.dart'; 16 | 17 | /// The [RenderObjectWidget] used by [Glass] to glassify its child. 18 | /// 19 | /// Most people will not need to use this class directly, but if you really need 20 | /// it, good luck. 21 | class GlassRenderObjectWidget extends MultiChildRenderObjectWidget { 22 | GlassRenderObjectWidget({ 23 | required this.shader, 24 | required Widget mask, 25 | required Widget child, 26 | required this.options, 27 | super.key, 28 | this.pixelRatio = 1.0, 29 | }) : super(children: [mask, child]); 30 | 31 | final ui.FragmentShader shader; 32 | final double pixelRatio; 33 | final GlassOptions options; 34 | 35 | @override 36 | RenderObject createRenderObject(BuildContext context) { 37 | return RenderGlass( 38 | shader: shader, 39 | pixelRatio: pixelRatio, 40 | blurSigma: options.blurSigma, 41 | contrastBoost: options.contrastBoost, 42 | saturationBoost: options.saturationBoost, 43 | grainIntensity: options.grainIntensity, 44 | brightnessCompensation: options.brightnessCompensation, 45 | edgeScale: options.edgeScale, 46 | centerScale: options.centerScale, 47 | glassTint: options.glassTint, 48 | refractionBorder: options.refractionBorder, 49 | boxShadow: options.boxShadow, 50 | ); 51 | } 52 | 53 | @override 54 | void updateRenderObject(BuildContext context, RenderGlass renderObject) { 55 | renderObject 56 | ..shader = shader 57 | ..pixelRatio = pixelRatio 58 | ..blurSigma = options.blurSigma 59 | ..contrastBoost = options.contrastBoost 60 | ..saturationBoost = options.saturationBoost 61 | ..grainIntensity = options.grainIntensity 62 | ..brightnessCompensation = options.brightnessCompensation 63 | ..edgeScale = options.edgeScale 64 | ..centerScale = options.centerScale 65 | ..glassTint = options.glassTint 66 | ..refractionBorder = options.refractionBorder 67 | ..boxShadow = options.boxShadow; 68 | } 69 | } 70 | 71 | class GlassRenderObjectParentData extends ContainerBoxParentData {} 72 | 73 | class RenderGlass extends RenderBox 74 | with 75 | ContainerRenderObjectMixin, 76 | RenderBoxContainerDefaultsMixin< 77 | RenderBox, 78 | GlassRenderObjectParentData 79 | > { 80 | RenderGlass({ 81 | required ui.FragmentShader shader, 82 | required double pixelRatio, 83 | required double blurSigma, 84 | required double contrastBoost, 85 | required double saturationBoost, 86 | required double grainIntensity, 87 | required double brightnessCompensation, 88 | required double edgeScale, 89 | required double centerScale, 90 | required Color? glassTint, 91 | required double refractionBorder, 92 | required BoxShadow? boxShadow, 93 | }) : _shader = shader, 94 | _pixelRatio = pixelRatio, 95 | _blurSigma = blurSigma, 96 | _contrastBoost = contrastBoost, 97 | _saturationBoost = saturationBoost, 98 | _grainIntensity = grainIntensity, 99 | _brightnessCompensation = brightnessCompensation, 100 | _edgeScale = edgeScale, 101 | _centerScale = centerScale, 102 | _glassTint = glassTint, 103 | _refractionBorder = refractionBorder, 104 | _boxShadow = boxShadow; 105 | 106 | ui.FragmentShader get shader => _shader; 107 | ui.FragmentShader _shader; 108 | set shader(ui.FragmentShader value) { 109 | if (_shader == value) return; 110 | _shader = value; 111 | markNeedsPaint(); 112 | } 113 | 114 | double get pixelRatio => _pixelRatio; 115 | double _pixelRatio; 116 | set pixelRatio(double value) { 117 | if (_pixelRatio == value) return; 118 | _pixelRatio = value; 119 | markNeedsPaint(); 120 | } 121 | 122 | double get blurSigma => _blurSigma; 123 | double _blurSigma; 124 | set blurSigma(double value) { 125 | if (_blurSigma == value) return; 126 | _blurSigma = value; 127 | markNeedsPaint(); 128 | } 129 | 130 | double get contrastBoost => _contrastBoost; 131 | double _contrastBoost; 132 | set contrastBoost(double value) { 133 | if (_contrastBoost == value) return; 134 | _contrastBoost = value; 135 | markNeedsPaint(); 136 | } 137 | 138 | double get saturationBoost => _saturationBoost; 139 | double _saturationBoost; 140 | set saturationBoost(double value) { 141 | if (_saturationBoost == value) return; 142 | _saturationBoost = value; 143 | markNeedsPaint(); 144 | } 145 | 146 | double get grainIntensity => _grainIntensity; 147 | double _grainIntensity; 148 | set grainIntensity(double value) { 149 | if (_grainIntensity == value) return; 150 | _grainIntensity = value; 151 | markNeedsPaint(); 152 | } 153 | 154 | double get brightnessCompensation => _brightnessCompensation; 155 | double _brightnessCompensation; 156 | set brightnessCompensation(double value) { 157 | if (_brightnessCompensation == value) return; 158 | _brightnessCompensation = value; 159 | markNeedsPaint(); 160 | } 161 | 162 | double get edgeScale => _edgeScale; 163 | double _edgeScale; 164 | set edgeScale(double value) { 165 | if (_edgeScale == value) return; 166 | _edgeScale = value; 167 | markNeedsPaint(); 168 | } 169 | 170 | double get centerScale => _centerScale; 171 | double _centerScale; 172 | set centerScale(double value) { 173 | if (_centerScale == value) return; 174 | _centerScale = value; 175 | markNeedsPaint(); 176 | } 177 | 178 | Color? get glassTint => _glassTint; 179 | Color? _glassTint; 180 | set glassTint(Color? value) { 181 | if (_glassTint == value) return; 182 | _glassTint = value; 183 | markNeedsPaint(); 184 | } 185 | 186 | double get refractionBorder => _refractionBorder; 187 | double _refractionBorder; 188 | set refractionBorder(double value) { 189 | if (_refractionBorder == value) return; 190 | _refractionBorder = value; 191 | markNeedsPaint(); 192 | } 193 | 194 | BoxShadow? get boxShadow => _boxShadow; 195 | BoxShadow? _boxShadow; 196 | set boxShadow(BoxShadow? value) { 197 | if (_boxShadow == value) return; 198 | _boxShadow = value; 199 | markNeedsPaint(); 200 | } 201 | 202 | @override 203 | void setupParentData(RenderBox child) { 204 | if (child.parentData is! GlassRenderObjectParentData) { 205 | child.parentData = GlassRenderObjectParentData(); 206 | } 207 | } 208 | 209 | RenderBox? get mask => firstChild; 210 | RenderBox? get actualChild => lastChild; 211 | 212 | @override 213 | bool get alwaysNeedsCompositing => true; 214 | 215 | @override 216 | bool get isRepaintBoundary => true; 217 | 218 | @override 219 | void performLayout() { 220 | if (childCount == 0) { 221 | size = constraints.biggest; 222 | return; 223 | } 224 | 225 | final maskBox = mask; 226 | final childBox = actualChild; 227 | 228 | if (childBox != null) { 229 | childBox.layout(constraints, parentUsesSize: true); 230 | size = childBox.size; 231 | (childBox.parentData! as GlassRenderObjectParentData).offset = 232 | Offset.zero; 233 | } else { 234 | size = constraints.smallest; 235 | } 236 | 237 | if (maskBox != null) { 238 | maskBox.layout(BoxConstraints.tight(size)); 239 | (maskBox.parentData! as GlassRenderObjectParentData).offset = Offset.zero; 240 | } 241 | } 242 | 243 | @override 244 | bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { 245 | final childBox = actualChild; 246 | 247 | if (childBox != null) { 248 | final childParentData = 249 | childBox.parentData! as GlassRenderObjectParentData; 250 | return result.addWithPaintOffset( 251 | offset: childParentData.offset, 252 | position: position, 253 | hitTest: (BoxHitTestResult result, Offset transformed) { 254 | assert( 255 | transformed == position - childParentData.offset, 256 | 'Hit test position does not match expected offset: ' 257 | '$transformed != ${position - childParentData.offset}', 258 | ); 259 | return childBox.hitTest(result, position: transformed); 260 | }, 261 | ); 262 | } 263 | 264 | return false; 265 | } 266 | 267 | @override 268 | void paint(PaintingContext context, Offset offset) { 269 | if (childCount == 0 || size.isEmpty) return; 270 | assert(offset == Offset.zero, 'RenderGlass should not be offset'); 271 | context 272 | // Add mask to the composited layer tree, it will be the first child 273 | ..pushLayer( 274 | ContainerLayer(), 275 | (PaintingContext context, Offset offset) { 276 | if (size.isEmpty) return; 277 | 278 | final maskBox = mask; 279 | if (maskBox != null) { 280 | final childParentData = 281 | maskBox.parentData! as GlassRenderObjectParentData; 282 | context.paintChild(maskBox, offset + childParentData.offset); 283 | } 284 | }, 285 | offset, 286 | ) 287 | // Add the actual child to the composited layer tree, it will be the 288 | // second child 289 | ..pushLayer( 290 | ContainerLayer(), 291 | (PaintingContext context, Offset offset) { 292 | if (size.isEmpty) return; 293 | 294 | final childBox = actualChild; 295 | if (childBox != null) { 296 | final childParentData = 297 | childBox.parentData! as GlassRenderObjectParentData; 298 | context.paintChild(childBox, offset + childParentData.offset); 299 | 300 | final shadow = _boxShadow; 301 | if (shadow != null) { 302 | context.canvas.saveLayer( 303 | null, 304 | Paint()..blendMode = BlendMode.dstIn, 305 | ); 306 | context.paintChild(mask!, offset + childParentData.offset); 307 | context.canvas.restore(); 308 | 309 | context.canvas.saveLayer(null, Paint()); 310 | 311 | context.canvas.saveLayer( 312 | null, 313 | Paint() 314 | ..imageFilter = ui.ImageFilter.blur( 315 | sigmaX: shadow.blurRadius, 316 | sigmaY: shadow.blurRadius, 317 | ), 318 | ); 319 | 320 | context.canvas.translate(shadow.offset.dx, shadow.offset.dy); 321 | context.paintChild(mask!, offset + childParentData.offset); 322 | 323 | context.canvas.saveLayer( 324 | null, 325 | Paint()..blendMode = BlendMode.srcIn, 326 | ); 327 | context.canvas.drawRect( 328 | Rect.fromLTWH( 329 | offset.dx + childParentData.offset.dx, 330 | offset.dy + childParentData.offset.dy, 331 | size.width, 332 | size.height, 333 | ), 334 | Paint()..color = shadow.color, 335 | ); 336 | context.canvas.restore(); 337 | context.canvas.restore(); 338 | 339 | context.canvas.saveLayer( 340 | null, 341 | Paint()..blendMode = BlendMode.dstOut, 342 | ); 343 | context.paintChild(mask!, offset + childParentData.offset); 344 | context.canvas.restore(); 345 | } 346 | } 347 | }, 348 | offset, 349 | ); 350 | } 351 | 352 | @override 353 | OffsetLayer updateCompositedLayer({OffsetLayer? oldLayer}) { 354 | _GlassFilterLayer layer; 355 | 356 | if (oldLayer is _GlassFilterLayer) { 357 | layer = oldLayer 358 | ..shader = _shader 359 | ..pixelRatio = _pixelRatio 360 | ..size = size 361 | ..blurSigma = _blurSigma 362 | ..contrastBoost = _contrastBoost 363 | ..saturationBoost = _saturationBoost 364 | ..grainIntensity = _grainIntensity 365 | ..brightnessCompensation = _brightnessCompensation 366 | ..edgeScale = _edgeScale 367 | ..centerScale = _centerScale 368 | ..glassTint = _glassTint 369 | ..refractionBorder = _refractionBorder; 370 | } else { 371 | layer = _GlassFilterLayer( 372 | shader: _shader, 373 | pixelRatio: _pixelRatio, 374 | size: size, 375 | blurSigma: _blurSigma, 376 | contrastBoost: _contrastBoost, 377 | saturationBoost: _saturationBoost, 378 | grainIntensity: _grainIntensity, 379 | brightnessCompensation: _brightnessCompensation, 380 | edgeScale: _edgeScale, 381 | centerScale: _centerScale, 382 | glassTint: _glassTint, 383 | refractionBorder: _refractionBorder, 384 | ); 385 | } 386 | 387 | return layer; 388 | } 389 | 390 | @override 391 | void dispose() { 392 | _shader.dispose(); 393 | super.dispose(); 394 | } 395 | } 396 | 397 | class _GlassFilterLayer extends OffsetLayer { 398 | _GlassFilterLayer({ 399 | required ui.FragmentShader shader, 400 | required double pixelRatio, 401 | required Size size, 402 | required double blurSigma, 403 | required double contrastBoost, 404 | required double saturationBoost, 405 | required double grainIntensity, 406 | required double brightnessCompensation, 407 | required double edgeScale, 408 | required double centerScale, 409 | required Color? glassTint, 410 | required double refractionBorder, 411 | }) : _shader = shader, 412 | _pixelRatio = pixelRatio, 413 | _size = size, 414 | _blurSigma = blurSigma, 415 | _contrastBoost = contrastBoost, 416 | _saturationBoost = saturationBoost, 417 | _grainIntensity = grainIntensity, 418 | _brightnessCompensation = brightnessCompensation, 419 | _edgeScale = edgeScale, 420 | _centerScale = centerScale, 421 | _glassTint = glassTint, 422 | _refractionBorder = refractionBorder, 423 | super(); 424 | 425 | ui.FragmentShader _shader; 426 | set shader(ui.FragmentShader value) { 427 | if (_shader == value) return; 428 | _shader = value; 429 | _clearLastImages(); 430 | markNeedsAddToScene(); 431 | } 432 | 433 | double _pixelRatio; 434 | set pixelRatio(double value) { 435 | if (_pixelRatio == value) return; 436 | _pixelRatio = value; 437 | _clearLastImages(); 438 | markNeedsAddToScene(); 439 | } 440 | 441 | Size _size; 442 | set size(Size value) { 443 | if (value == _size) return; 444 | _size = value; 445 | _clearLastImages(); 446 | markNeedsAddToScene(); 447 | } 448 | 449 | double _blurSigma; 450 | set blurSigma(double value) { 451 | if (_blurSigma == value) return; 452 | _blurSigma = value; 453 | markNeedsAddToScene(); 454 | } 455 | 456 | double _contrastBoost; 457 | set contrastBoost(double value) { 458 | if (_contrastBoost == value) return; 459 | _contrastBoost = value; 460 | markNeedsAddToScene(); 461 | } 462 | 463 | double _saturationBoost; 464 | set saturationBoost(double value) { 465 | if (_saturationBoost == value) return; 466 | _saturationBoost = value; 467 | markNeedsAddToScene(); 468 | } 469 | 470 | double _grainIntensity; 471 | set grainIntensity(double value) { 472 | if (_grainIntensity == value) return; 473 | _grainIntensity = value; 474 | markNeedsAddToScene(); 475 | } 476 | 477 | double _brightnessCompensation; 478 | set brightnessCompensation(double value) { 479 | if (_brightnessCompensation == value) return; 480 | _brightnessCompensation = value; 481 | markNeedsAddToScene(); 482 | } 483 | 484 | double _edgeScale; 485 | set edgeScale(double value) { 486 | if (_edgeScale == value) return; 487 | _edgeScale = value; 488 | markNeedsAddToScene(); 489 | } 490 | 491 | double _centerScale; 492 | set centerScale(double value) { 493 | if (_centerScale == value) return; 494 | _centerScale = value; 495 | markNeedsAddToScene(); 496 | } 497 | 498 | Color? _glassTint; 499 | set glassTint(Color? value) { 500 | if (_glassTint == value) return; 501 | _glassTint = value; 502 | markNeedsAddToScene(); 503 | } 504 | 505 | double _refractionBorder; 506 | set refractionBorder(double value) { 507 | if (_refractionBorder == value) return; 508 | _refractionBorder = value; 509 | markNeedsAddToScene(); 510 | } 511 | 512 | ui.BackdropFilterEngineLayer? _backdropFilterLayer; 513 | ui.ImageFilterEngineLayer? _imageFilterLayer; 514 | ui.Image? _lastMaskImage; 515 | ui.Image? _lastBlurredMaskImage; 516 | ui.Image? _lastSecondaryBlurredMaskImage; 517 | 518 | void _clearLastImages() { 519 | _lastMaskImage?.dispose(); 520 | _lastMaskImage = null; 521 | 522 | _lastBlurredMaskImage?.dispose(); 523 | _lastBlurredMaskImage = null; 524 | 525 | _lastSecondaryBlurredMaskImage?.dispose(); 526 | _lastSecondaryBlurredMaskImage = null; 527 | } 528 | 529 | @override 530 | void addToScene(ui.SceneBuilder builder) { 531 | final offsetLayer = builder.pushOffset( 532 | offset.dx, 533 | offset.dy, 534 | oldLayer: engineLayer as ui.OffsetEngineLayer?, 535 | ); 536 | engineLayer = offsetLayer; 537 | { 538 | _shader.setFloatUniforms(initialIndex: 2, (uniforms) { 539 | final rect = Rect.fromLTWH( 540 | offset.dx, 541 | offset.dy, 542 | _size.width, 543 | _size.height, 544 | ); 545 | uniforms 546 | ..setFloat(rect.left * _pixelRatio) 547 | ..setFloat(rect.top * _pixelRatio) 548 | ..setFloat(rect.width * _pixelRatio) 549 | ..setFloat(rect.height * _pixelRatio) 550 | ..setFloat(_blurSigma) 551 | ..setFloat(_contrastBoost) 552 | ..setFloat(_saturationBoost) 553 | ..setFloat(_grainIntensity) 554 | ..setFloat(_brightnessCompensation) 555 | ..setFloat(_edgeScale) 556 | ..setFloat(_centerScale) 557 | ..setColor(_glassTint ?? const ui.Color(0x00000000)); 558 | }); 559 | 560 | final mask = _buildMaskImage(); 561 | _lastMaskImage?.dispose(); 562 | _lastMaskImage = mask; 563 | 564 | final blurMask = _buildMaskImage(_refractionBorder); 565 | _lastBlurredMaskImage?.dispose(); 566 | _lastBlurredMaskImage = blurMask; 567 | 568 | final secondaryBlurMask = _buildMaskImage(5); 569 | _lastSecondaryBlurredMaskImage?.dispose(); 570 | _lastSecondaryBlurredMaskImage = secondaryBlurMask; 571 | 572 | _shader 573 | ..setImageSampler(1, mask) 574 | ..setImageSampler(2, blurMask) 575 | ..setImageSampler(3, secondaryBlurMask); 576 | 577 | _backdropFilterLayer = builder.pushBackdropFilter( 578 | ui.ImageFilter.shader(_shader), 579 | oldLayer: _backdropFilterLayer, 580 | ); 581 | { 582 | addActualChildToScene(builder); 583 | } 584 | builder.pop(); 585 | } 586 | builder.pop(); 587 | } 588 | 589 | ui.Image _buildMaskImage([double? refractionBorder]) { 590 | final builder = ui.SceneBuilder(); 591 | final transform = Matrix4.diagonal3Values(_pixelRatio, _pixelRatio, 1); 592 | final bounds = offset & _size; 593 | 594 | 595 | builder.pushTransform(transform.storage); 596 | addMaskToScene(builder, refractionBorder); 597 | builder.pop(); 598 | 599 | return builder.build().toImageSync( 600 | (_pixelRatio * bounds.width).floor(), 601 | (_pixelRatio * bounds.height).floor(), 602 | ); 603 | } 604 | 605 | void addMaskToScene(ui.SceneBuilder builder, [double? refractionBorder]) { 606 | final mask = firstChild; 607 | 608 | if (refractionBorder != null) { 609 | _imageFilterLayer = builder.pushImageFilter( 610 | ui.ImageFilter.blur(sigmaX: refractionBorder, sigmaY: refractionBorder), 611 | oldLayer: _imageFilterLayer, 612 | ); 613 | } 614 | 615 | 616 | mask?.addToScene(builder); 617 | 618 | if (refractionBorder != null) { 619 | builder.pop(); 620 | } 621 | } 622 | 623 | void addActualChildToScene(ui.SceneBuilder builder) { 624 | firstChild?.nextSibling?.addToScene(builder); 625 | } 626 | 627 | @override 628 | void detach() { 629 | _clearLastImages(); 630 | super.detach(); 631 | } 632 | 633 | @override 634 | void dispose() { 635 | _clearLastImages(); 636 | _backdropFilterLayer = null; 637 | _imageFilterLayer = null; 638 | super.dispose(); 639 | } 640 | } 641 | --------------------------------------------------------------------------------