├── 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 |
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