use fragment shader based dot grid background
This commit is contained in:
+91
-33
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import './render.dart';
|
import 'dart:ui' as ui show FragmentProgram; // for runtime shader
|
||||||
|
import 'package:flutter/rendering.dart' show RendererBinding; // to mark repaint after async load
|
||||||
|
|
||||||
/// Abstract definition for a [LazyCanvas] background.
|
/// Abstract definition for a [LazyCanvas] background.
|
||||||
abstract class CanvasBackground {
|
abstract class CanvasBackground {
|
||||||
@@ -35,11 +36,15 @@ class SingleColorBackround extends CanvasBackground {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DotGridBackground extends CanvasBackground {
|
class DotGridBackground extends CanvasBackground {
|
||||||
final double size;
|
final double size; // radius in logical pixels at scale = 1
|
||||||
final double spacing;
|
final double spacing; // grid spacing in logical pixels at scale = 1
|
||||||
final Color dotColor;
|
final Color dotColor;
|
||||||
final Color backgroundColor;
|
final Color backgroundColor;
|
||||||
|
|
||||||
|
// Static shader program cache shared across instances
|
||||||
|
static ui.FragmentProgram? _program;
|
||||||
|
static Future<ui.FragmentProgram>? _programFuture;
|
||||||
|
|
||||||
const DotGridBackground({
|
const DotGridBackground({
|
||||||
this.size = 2.0,
|
this.size = 2.0,
|
||||||
this.spacing = 50.0,
|
this.spacing = 50.0,
|
||||||
@@ -47,36 +52,89 @@ class DotGridBackground extends CanvasBackground {
|
|||||||
this.backgroundColor = Colors.white,
|
this.backgroundColor = Colors.white,
|
||||||
}) : super();
|
}) : super();
|
||||||
|
|
||||||
@override
|
// Kick off async load once
|
||||||
void paint(Canvas canvas, Offset screenOffset, Offset canvasOffset, double scale, Size canvasSize) {
|
static void _ensureProgramLoaded() {
|
||||||
final paint = Paint()
|
if (_program != null || _programFuture != null) return;
|
||||||
..color = backgroundColor
|
try {
|
||||||
..strokeWidth = 1.0 * scale;
|
_programFuture = ui.FragmentProgram.fromAsset('packages/infinite_lazy_grid/shaders/dot_grid.frag')
|
||||||
double scaledSpacing = spacing * scale;
|
..then((p) {
|
||||||
double scaledSize = size * scale;
|
_program = p;
|
||||||
|
// Request a repaint when shader becomes available
|
||||||
// Use canvasOffset to determine the grid position in grid space
|
try {
|
||||||
// Calculate the grid origin in canvas space (0,0 should be at a grid point)
|
RendererBinding.instance.renderView.markNeedsPaint();
|
||||||
// Find the first grid point that aligns with the coordinate system
|
} catch (_) {}
|
||||||
double firstGridX = (canvasOffset.dx / spacing).floor() * spacing;
|
}).catchError((_) {
|
||||||
double firstGridY = (canvasOffset.dy / spacing).floor() * spacing;
|
// keep _program null; we'll fallback to CPU/simple paint
|
||||||
|
});
|
||||||
// Convert grid space coordinates to screen space
|
} catch (_) {
|
||||||
double screenStartX = (firstGridX - canvasOffset.dx) * scale + screenOffset.dx;
|
// fromAsset may throw synchronously on unsupported platforms
|
||||||
double screenStartY = (firstGridY - canvasOffset.dy) * scale + screenOffset.dy;
|
_programFuture = null;
|
||||||
|
_program = null;
|
||||||
// Ensure we start drawing before the visible area to avoid gaps
|
|
||||||
while (screenStartX > screenOffset.dx) {
|
|
||||||
screenStartX -= scaledSpacing;
|
|
||||||
}
|
|
||||||
while (screenStartY > screenOffset.dy) {
|
|
||||||
screenStartY -= scaledSpacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (double x = screenStartX; x < screenOffset.dx + canvasSize.width + scaledSpacing; x += scaledSpacing) {
|
|
||||||
for (double y = screenStartY; y < screenOffset.dy + canvasSize.height + scaledSpacing; y += scaledSpacing) {
|
|
||||||
canvas.drawCircle(Offset(x, y), scaledSize, paint..color = dotColor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Offset screenOffset, Offset canvasOffset, double scale, Size canvasSize) {
|
||||||
|
// Always draw background fill (also serves as fallback while shader loads)
|
||||||
|
final bgPaint = Paint()
|
||||||
|
..color = backgroundColor
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
final rect = Rect.fromLTWH(screenOffset.dx, screenOffset.dy, canvasSize.width, canvasSize.height);
|
||||||
|
|
||||||
|
// Ensure shader load started
|
||||||
|
_ensureProgramLoaded();
|
||||||
|
|
||||||
|
final program = _program;
|
||||||
|
if (program == null) {
|
||||||
|
// Fallback: just fill background without dots until shader is ready
|
||||||
|
canvas.drawRect(rect, bgPaint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device pixel ratio for converting logical -> physical pixels used by FlutterFragCoord
|
||||||
|
final dpr = RendererBinding.instance.renderView.configuration.devicePixelRatio;
|
||||||
|
|
||||||
|
// Compute uniforms in physical pixels
|
||||||
|
final scaledSpacingPx = (spacing * scale * dpr).abs();
|
||||||
|
final radiusPx = (size * scale * dpr).abs();
|
||||||
|
|
||||||
|
// Phase to align grid with world origin under pan/zoom
|
||||||
|
double modPositive(double a, double m) => ((a % m) + m) % m;
|
||||||
|
final phaseXPx = modPositive(canvasOffset.dx * scale * dpr, scaledSpacingPx == 0 ? 1 : scaledSpacingPx);
|
||||||
|
final phaseYPx = modPositive(canvasOffset.dy * scale * dpr, scaledSpacingPx == 0 ? 1 : scaledSpacingPx);
|
||||||
|
|
||||||
|
final originXPx = screenOffset.dx * dpr;
|
||||||
|
final originYPx = screenOffset.dy * dpr;
|
||||||
|
|
||||||
|
// Build fragment shader and set uniforms in declaration order
|
||||||
|
final shader = program.fragmentShader();
|
||||||
|
int i = 0;
|
||||||
|
void set1(double a) {
|
||||||
|
shader.setFloat(i++, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set2(double a, double b) {
|
||||||
|
shader.setFloat(i++, a);
|
||||||
|
shader.setFloat(i++, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set4c(Color c) {
|
||||||
|
shader.setFloat(i++, c.r);
|
||||||
|
shader.setFloat(i++, c.g);
|
||||||
|
shader.setFloat(i++, c.b);
|
||||||
|
shader.setFloat(i++, c.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
set2(originXPx, originYPx);
|
||||||
|
set1(scaledSpacingPx);
|
||||||
|
set2(phaseXPx, phaseYPx);
|
||||||
|
set1(radiusPx);
|
||||||
|
set4c(dotColor);
|
||||||
|
set4c(backgroundColor);
|
||||||
|
|
||||||
|
final paint = Paint()..shader = shader;
|
||||||
|
|
||||||
|
// Draw once with the shader
|
||||||
|
canvas.drawRect(rect, paint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-29
@@ -22,34 +22,36 @@ dev_dependencies:
|
|||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
# The following section is specific to Flutter packages.
|
||||||
flutter:
|
flutter:
|
||||||
|
shaders:
|
||||||
|
- shaders/dot_grid.frag
|
||||||
|
|
||||||
# To add assets to your package, add an assets section, like this:
|
# To add assets to your package, add an assets section, like this:
|
||||||
# assets:
|
# assets:
|
||||||
# - images/a_dot_burr.jpeg
|
# - images/a_dot_burr.jpeg
|
||||||
# - images/a_dot_ham.jpeg
|
# - images/a_dot_ham.jpeg
|
||||||
#
|
#
|
||||||
# For details regarding assets in packages, see
|
# For details regarding assets in packages, see
|
||||||
# https://flutter.dev/to/asset-from-package
|
# https://flutter.dev/to/asset-from-package
|
||||||
#
|
#
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/to/resolution-aware-images
|
# https://flutter.dev/to/resolution-aware-images
|
||||||
|
|
||||||
# To add custom fonts to your package, add a fonts section here,
|
# To add custom fonts to your package, add a fonts section here,
|
||||||
# in this "flutter" section. Each entry in this list should have a
|
# in this "flutter" section. Each entry in this list should have a
|
||||||
# "family" key with the font family name, and a "fonts" key with a
|
# "family" key with the font family name, and a "fonts" key with a
|
||||||
# list giving the asset and other descriptors for the font. For
|
# list giving the asset and other descriptors for the font. For
|
||||||
# example:
|
# example:
|
||||||
# fonts:
|
# fonts:
|
||||||
# - family: Schyler
|
# - family: Schyler
|
||||||
# fonts:
|
# fonts:
|
||||||
# - asset: fonts/Schyler-Regular.ttf
|
# - asset: fonts/Schyler-Regular.ttf
|
||||||
# - asset: fonts/Schyler-Italic.ttf
|
# - asset: fonts/Schyler-Italic.ttf
|
||||||
# style: italic
|
# style: italic
|
||||||
# - family: Trajan Pro
|
# - family: Trajan Pro
|
||||||
# fonts:
|
# fonts:
|
||||||
# - asset: fonts/TrajanPro.ttf
|
# - asset: fonts/TrajanPro.ttf
|
||||||
# - asset: fonts/TrajanPro_Bold.ttf
|
# - asset: fonts/TrajanPro_Bold.ttf
|
||||||
# weight: 700
|
# weight: 700
|
||||||
#
|
#
|
||||||
# For details regarding fonts in packages, see
|
# For details regarding fonts in packages, see
|
||||||
# https://flutter.dev/to/font-from-package
|
# https://flutter.dev/to/font-from-package
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#include <flutter/runtime_effect.glsl>
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
uniform vec2 uScreenOrigin; // screenOffset in pixels
|
||||||
|
uniform float uScaledSpacing; // spacing * scale in pixels
|
||||||
|
uniform vec2 uPhasePx; // phase shift in pixels: mod(-canvasOffset * scale, scaledSpacing)
|
||||||
|
uniform float uRadiusPx; // dot radius in pixels: size * scale
|
||||||
|
uniform vec4 uDotColor; // RGBA 0..1
|
||||||
|
uniform vec4 uBgColor; // RGBA 0..1
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 p = FlutterFragCoord().xy - uScreenOrigin; // local pixel coords within the painted rect
|
||||||
|
|
||||||
|
// LOD: if dots become sub-pixel or spacing too dense, draw only background
|
||||||
|
if (uScaledSpacing < 1.0 || uRadiusPx < 0.25) {
|
||||||
|
fragColor = uBgColor;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Periodic grid in screen space with phase to account for canvasOffset
|
||||||
|
vec2 rem = mod(p + uPhasePx, uScaledSpacing);
|
||||||
|
vec2 d = min(rem, vec2(uScaledSpacing) - rem);
|
||||||
|
float dist = length(d);
|
||||||
|
|
||||||
|
// Analytic AA around the circle edge (~1px transition)
|
||||||
|
float edge = uRadiusPx;
|
||||||
|
float alpha = 1.0 - smoothstep(edge - 1.0, edge + 1.0, dist);
|
||||||
|
|
||||||
|
fragColor = mix(uBgColor, uDotColor, alpha);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user