aves/plugins/aves_magnifier/lib/src/controller/controller_delegate.dart
2023-11-05 01:01:24 +01:00

137 lines
5.3 KiB
Dart

import 'dart:async';
import 'package:aves_magnifier/src/controller/controller.dart';
import 'package:aves_magnifier/src/controller/state.dart';
import 'package:aves_magnifier/src/core/core.dart';
import 'package:aves_magnifier/src/scale/scale_boundaries.dart';
import 'package:aves_magnifier/src/scale/state.dart';
import 'package:flutter/widgets.dart';
/// A class to hold internal layout logic to sync both controller states
///
/// It reacts to layout changes (eg: enter landscape or widget resize) and syncs the two controllers.
mixin AvesMagnifierControllerDelegate on State<AvesMagnifier> {
AvesMagnifierController get controller => widget.controller;
ScaleBoundaries? get scaleBoundaries => controller.scaleBoundaries;
ScaleStateCycle get scaleStateCycle => widget.scaleStateCycle;
Alignment get basePosition => ScaleBoundaries.basePosition;
Function(double? prevScale, double? nextScale, Offset nextPosition)? _animateScale;
/// Mark if scale need recalculation, useful for scale boundaries changes.
bool markNeedsScaleRecalc = true;
final List<StreamSubscription> _subscriptions = [];
void registerDelegate(AvesMagnifier widget) {
_subscriptions.add(widget.controller.stateStream.listen(_onMagnifierStateChanged));
_subscriptions.add(widget.controller.scaleStateChangeStream.listen(_onScaleStateChanged));
}
void unregisterDelegate(AvesMagnifier oldWidget) {
_animateScale = null;
_subscriptions
..forEach((sub) => sub.cancel())
..clear();
}
void _onScaleStateChanged(ScaleStateChange scaleStateChange) {
if (scaleStateChange.source == ChangeSource.internal) return;
if (!controller.hasScaleSateChanged) return;
if (_animateScale == null || controller.isZooming) {
controller.update(scale: scale, source: scaleStateChange.source);
return;
}
final nextScaleState = scaleStateChange.state;
final nextScale = controller.getScaleForScaleState(nextScaleState);
var nextPosition = Offset.zero;
if (nextScaleState == ScaleState.covering || nextScaleState == ScaleState.originalSize) {
final childFocalPoint = scaleStateChange.childFocalPoint;
final boundaries = scaleBoundaries;
if (childFocalPoint != null && boundaries != null) {
nextPosition = boundaries.contentToStatePosition(nextScale!, childFocalPoint);
}
}
final prevScale = controller.scale ?? controller.getScaleForScaleState(controller.previousScaleState.state);
_animateScale!(prevScale, nextScale, nextPosition);
}
void setScaleStateUpdateAnimation(void Function(double? prevScale, double? nextScale, Offset nextPosition) animateScale) {
_animateScale = animateScale;
}
void _onMagnifierStateChanged(MagnifierState state) {
final boundaries = scaleBoundaries;
if (boundaries == null) return;
controller.update(position: boundaries.clampPosition(position: position, scale: scale!), source: state.source);
if (controller.scale == controller.previousState.scale) return;
if (state.source == ChangeSource.internal || state.source == ChangeSource.animation) return;
final newScaleState = (scale! > boundaries.initialScale) ? ScaleState.zoomedIn : ScaleState.zoomedOut;
controller.setScaleState(newScaleState, state.source);
}
Offset get position => controller.position;
double? recalcScale() {
final scaleState = controller.scaleState.state;
final newScale = controller.getScaleForScaleState(scaleState);
markNeedsScaleRecalc = false;
setScale(newScale, ChangeSource.internal);
return newScale;
}
double? get scale {
final scaleState = controller.scaleState.state;
final needsRecalc = markNeedsScaleRecalc && !(scaleState == ScaleState.zoomedIn || scaleState == ScaleState.zoomedOut);
final scaleExistsOnController = controller.scale != null;
if (needsRecalc || !scaleExistsOnController) {
return recalcScale();
}
return controller.scale;
}
void setScale(double? scale, ChangeSource source) => controller.update(scale: scale, source: source);
void updateScaleStateFromNewScale(double newScale, ChangeSource source) {
final boundaries = scaleBoundaries;
if (boundaries == null) return;
var newScaleState = ScaleState.initial;
if (scale != boundaries.initialScale) {
newScaleState = (newScale > boundaries.initialScale) ? ScaleState.zoomedIn : ScaleState.zoomedOut;
}
controller.setScaleState(newScaleState, source);
}
void nextScaleState(ChangeSource source, {Offset? childFocalPoint}) {
final scaleState = controller.scaleState.state;
if (scaleState == ScaleState.zoomedIn || scaleState == ScaleState.zoomedOut) {
controller.setScaleState(scaleStateCycle(scaleState), source, childFocalPoint: childFocalPoint);
return;
}
final originalScale = controller.getScaleForScaleState(scaleState);
var prevScale = originalScale;
var prevScaleState = scaleState;
var nextScale = originalScale;
var nextScaleState = scaleState;
do {
prevScale = nextScale;
prevScaleState = nextScaleState;
nextScaleState = scaleStateCycle(prevScaleState);
nextScale = controller.getScaleForScaleState(nextScaleState);
} while (prevScale == nextScale && scaleState != nextScaleState);
if (originalScale == nextScale) return;
controller.setScaleState(nextScaleState, source, childFocalPoint: childFocalPoint);
}
}