aves/plugins/aves_magnifier/lib/src/scale/scale_boundaries.dart
2025-02-18 23:50:08 +01:00

159 lines
5.5 KiB
Dart

import 'dart:math';
import 'package:aves_magnifier/src/controller/controller.dart';
import 'package:aves_magnifier/src/controller/range.dart';
import 'package:aves_magnifier/src/scale/scale_level.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/widgets.dart';
/// Internal class to wrap custom scale boundaries (min, max and initial)
/// Also, stores values regarding the two sizes: the container and the content.
@immutable
class ScaleBoundaries extends Equatable {
final bool _allowOriginalScaleBeyondRange;
final ScaleLevel _minScale;
final ScaleLevel _maxScale;
final ScaleLevel _initialScale;
final Size viewportSize;
final Size contentSize;
final EdgeInsets Function(double scale)? padding;
final Matrix4? externalTransform;
static const Alignment basePosition = Alignment.center;
@override
List<Object?> get props => [_allowOriginalScaleBeyondRange, _minScale, _maxScale, _initialScale, viewportSize, contentSize, padding, externalTransform];
const ScaleBoundaries({
required bool allowOriginalScaleBeyondRange,
required ScaleLevel minScale,
required ScaleLevel maxScale,
required ScaleLevel initialScale,
required this.viewportSize,
required this.contentSize,
this.padding,
this.externalTransform,
}) : _allowOriginalScaleBeyondRange = allowOriginalScaleBeyondRange,
_minScale = minScale,
_maxScale = maxScale,
_initialScale = initialScale;
static const ScaleBoundaries zero = ScaleBoundaries(
allowOriginalScaleBeyondRange: true,
minScale: ScaleLevel(factor: .0),
maxScale: ScaleLevel(factor: double.infinity),
initialScale: ScaleLevel(ref: ScaleReference.contained),
viewportSize: Size.zero,
contentSize: Size.zero,
padding: null,
);
ScaleBoundaries copyWith({
bool? allowOriginalScaleBeyondRange,
ScaleLevel? minScale,
ScaleLevel? maxScale,
ScaleLevel? initialScale,
Size? viewportSize,
Size? contentSize,
EdgeInsets Function(double scale)? padding,
Matrix4? externalTransform,
}) {
return ScaleBoundaries(
allowOriginalScaleBeyondRange: allowOriginalScaleBeyondRange ?? _allowOriginalScaleBeyondRange,
minScale: minScale ?? _minScale,
maxScale: maxScale ?? _maxScale,
initialScale: initialScale ?? _initialScale,
viewportSize: viewportSize ?? this.viewportSize,
contentSize: contentSize ?? this.contentSize,
padding: padding ?? this.padding,
externalTransform: externalTransform ?? this.externalTransform,
);
}
Size get _transformedViewportSize {
final matrix = externalTransform;
return matrix != null ? MatrixUtils.transformRect(Matrix4.inverted(matrix), Offset.zero & viewportSize).size : viewportSize;
}
double scaleForLevel(ScaleLevel level) {
final factor = level.factor;
switch (level.ref) {
case ScaleReference.contained:
return factor * ScaleLevel.scaleForContained(viewportSize, contentSize);
case ScaleReference.covered:
return factor * ScaleLevel.scaleForCovering(viewportSize, contentSize);
case ScaleReference.absolute:
return factor;
}
}
double get originalScale {
final view = WidgetsBinding.instance.platformDispatcher.views.firstOrNull;
return 1.0 / (view?.devicePixelRatio ?? 1.0);
}
double get initialScale => scaleForLevel(_initialScale);
Offset get viewportCenter => viewportSize.center(Offset.zero);
Offset get _contentCenter => contentSize.center(Offset.zero);
Offset viewportToContentPosition(AvesMagnifierController controller, Offset viewportPosition) {
return (viewportPosition - viewportCenter - controller.position) / controller.scale! + _contentCenter;
}
Offset contentToStatePosition(double scale, Offset contentPosition) {
return (_contentCenter - contentPosition) * scale;
}
EdgeRange getXEdges({required double scale}) {
final computedWidth = contentSize.width * scale;
final viewportWidth = _transformedViewportSize.width;
final positionX = basePosition.x;
final widthDiff = max(0, computedWidth - viewportWidth);
final minX = ((positionX - 1).abs() / 2) * widthDiff * -1;
final maxX = ((positionX + 1).abs() / 2) * widthDiff;
final _padding = padding?.call(scale) ?? EdgeInsets.zero;
return EdgeRange(minX - _padding.left, maxX + _padding.right);
}
EdgeRange getYEdges({required double scale}) {
final computedHeight = contentSize.height * scale;
final viewportHeight = _transformedViewportSize.height;
final positionY = basePosition.y;
final heightDiff = max(0, computedHeight - viewportHeight);
final minY = ((positionY - 1).abs() / 2) * heightDiff * -1;
final maxY = ((positionY + 1).abs() / 2) * heightDiff;
final _padding = padding?.call(scale) ?? EdgeInsets.zero;
return EdgeRange(minY - _padding.top, maxY + _padding.bottom);
}
double clampScale(double scale) {
final minScale = {
scaleForLevel(_minScale),
_allowOriginalScaleBeyondRange ? originalScale : double.infinity,
initialScale,
}.fold(double.infinity, min);
final maxScale = {
scaleForLevel(_maxScale),
_allowOriginalScaleBeyondRange ? originalScale : double.negativeInfinity,
initialScale,
}.fold(.0, max);
return scale.clamp(minScale, maxScale);
}
Offset clampPosition({required Offset position, required double scale}) {
final rangeX = getXEdges(scale: scale);
final rangeY = getYEdges(scale: scale);
return Offset(
position.dx.clamp(rangeX.min, rangeX.max),
position.dy.clamp(rangeY.min, rangeY.max),
);
}
}