/// Where to look in the complex plane — pure Dart, shared by every backend. /// /// Lives apart from the bridge so the web build never drags `dart:ffi` in /// through a geometry type. library; /// Where to look in the complex plane, and how hard to look. class FractalView { const FractalView({ required this.width, required this.height, this.centerX = -0.5, this.centerY = 0.0, this.scale = 3.0, this.maxIter = 500, }); final int width; final int height; /// Centre of the viewport in the complex plane. final double centerX; final double centerY; /// Width of the viewport in the complex plane. Smaller = deeper zoom. final double scale; /// Escape-time iteration cap. The cost of a frame scales with this. final int maxIter; /// Returns this view zoomed by [factor] about a point given in *fractional* /// viewport coordinates — (0,0) top-left, (1,1) bottom-right. /// /// The point under the cursor stays under the cursor: that is the whole /// contract, and it is what makes click-to-zoom feel anchored rather than /// drifting. `factor < 1` zooms in. FractalView zoomedAt(double fx, double fy, double factor) { final aspect = width / height; final ox = fx - 0.5; final oy = fy - 0.5; // The complex-plane point currently under (fx, fy). final targetX = centerX + ox * scale * aspect; final targetY = centerY + oy * scale; final newScale = scale * factor; return copyWith( scale: newScale, centerX: targetX - ox * newScale * aspect, centerY: targetY - oy * newScale, ); } /// Returns this view dragged by a delta given as a *fraction* of the /// viewport — dragging right by half the width is `fdx = 0.5`. /// /// The image follows the finger, so the centre moves the opposite way. FractalView pannedBy(double fdx, double fdy) { final aspect = width / height; return copyWith( centerX: centerX - fdx * scale * aspect, centerY: centerY - fdy * scale, ); } FractalView copyWith({ int? width, int? height, double? centerX, double? centerY, double? scale, int? maxIter, }) => FractalView( width: width ?? this.width, height: height ?? this.height, centerX: centerX ?? this.centerX, centerY: centerY ?? this.centerY, scale: scale ?? this.scale, maxIter: maxIter ?? this.maxIter, ); }