diff --git a/lib/main_play_test_editor.dart b/lib/main_play_test_editor.dart index 01ef9fbfa..dd60230e1 100644 --- a/lib/main_play_test_editor.dart +++ b/lib/main_play_test_editor.dart @@ -11,7 +11,7 @@ void main() => mainCommon( debugIntentData: { IntentDataKeys.action: IntentActions.edit, IntentDataKeys.mimeType: 'image/*', - IntentDataKeys.uri: 'content://media/external/images/media/183128', - // IntentDataKeys.uri: 'content://media/external/images/media/183534', + IntentDataKeys.uri: 'content://media/external/images/media/1000064996', // landscape + // IntentDataKeys.uri: 'content://media/external/images/media/1000064754', // portrait }, ); diff --git a/lib/widgets/editor/control_panel.dart b/lib/widgets/editor/control_panel.dart index 20ef45aca..e938b88d7 100644 --- a/lib/widgets/editor/control_panel.dart +++ b/lib/widgets/editor/control_panel.dart @@ -7,6 +7,7 @@ import 'package:aves/widgets/common/identity/buttons/overlay_button.dart'; import 'package:aves/widgets/editor/transform/control_panel.dart'; import 'package:aves/widgets/editor/transform/controller.dart'; import 'package:aves/widgets/viewer/overlay/viewer_buttons.dart'; +import 'package:aves_magnifier/aves_magnifier.dart'; import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -125,6 +126,7 @@ class EditorControlPanel extends StatelessWidget { void _cancelAction(BuildContext context) { actionNotifier.value = null; + context.read().reset(); context.read().reset(); } diff --git a/lib/widgets/editor/entry_editor_page.dart b/lib/widgets/editor/entry_editor_page.dart index 5acce2bca..a77a3a939 100644 --- a/lib/widgets/editor/entry_editor_page.dart +++ b/lib/widgets/editor/entry_editor_page.dart @@ -30,7 +30,7 @@ class ImageEditorPage extends StatefulWidget { class _ImageEditorPageState extends State { final List _subscriptions = []; final ValueNotifier _actionNotifier = ValueNotifier(null); - final ValueNotifier _paddingNotifier = ValueNotifier(EdgeInsets.zero); + final ValueNotifier _marginNotifier = ValueNotifier(EdgeInsets.zero); final ValueNotifier _viewStateNotifier = ValueNotifier(ViewState.zero); final AvesMagnifierController _magnifierController = AvesMagnifierController(); late final TransformController _transformController; @@ -49,7 +49,7 @@ class _ImageEditorPageState extends State { ..forEach((sub) => sub.cancel()) ..clear(); _actionNotifier.dispose(); - _paddingNotifier.dispose(); + _marginNotifier.dispose(); _viewStateNotifier.dispose(); _magnifierController.dispose(); _transformController.dispose(); @@ -59,8 +59,11 @@ class _ImageEditorPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: Provider.value( - value: _transformController, + body: MultiProvider( + providers: [ + Provider.value(value: _magnifierController), + Provider.value(value: _transformController), + ], child: SafeArea( child: Column( children: [ @@ -72,7 +75,7 @@ class _ImageEditorPageState extends State { magnifierController: _magnifierController, transformController: _transformController, actionNotifier: _actionNotifier, - paddingNotifier: _paddingNotifier, + marginNotifier: _marginNotifier, viewStateNotifier: _viewStateNotifier, entry: widget.entry, ), @@ -91,7 +94,7 @@ class _ImageEditorPageState extends State { return Cropper( magnifierController: _magnifierController, transformController: _transformController, - paddingNotifier: _paddingNotifier, + marginNotifier: _marginNotifier, ); case null: return const SizedBox(); @@ -101,6 +104,7 @@ class _ImageEditorPageState extends State { ], ), ), + const Divider(height: 0), EditorControlPanel( entry: widget.entry, actionNotifier: _actionNotifier, @@ -113,13 +117,13 @@ class _ImageEditorPageState extends State { ); } - void _onActionChanged() => _updateImagePadding(); + void _onActionChanged() => _updateImageMargin(); - void _updateImagePadding() { + void _updateImageMargin() { if (_actionNotifier.value == EditorAction.transform) { - _paddingNotifier.value = Cropper.imagePadding; + _marginNotifier.value = Cropper.imageMargin; } else { - _paddingNotifier.value = EdgeInsets.zero; + _marginNotifier.value = EdgeInsets.zero; } } diff --git a/lib/widgets/editor/image.dart b/lib/widgets/editor/image.dart index c8b01d45f..b39286363 100644 --- a/lib/widgets/editor/image.dart +++ b/lib/widgets/editor/image.dart @@ -17,7 +17,7 @@ class EditorImage extends StatefulWidget { final AvesMagnifierController magnifierController; final TransformController transformController; final ValueNotifier actionNotifier; - final ValueNotifier paddingNotifier; + final ValueNotifier marginNotifier; final ValueNotifier viewStateNotifier; final AvesEntry entry; @@ -26,7 +26,7 @@ class EditorImage extends StatefulWidget { required this.magnifierController, required this.transformController, required this.actionNotifier, - required this.paddingNotifier, + required this.marginNotifier, required this.viewStateNotifier, required this.entry, }); @@ -96,8 +96,8 @@ class _EditorImageState extends State { final canvasSize = MatrixUtils.transformRect(imageToUserMatrix, Offset.zero & mediaSize).size; return ValueListenableBuilder( - valueListenable: widget.paddingNotifier, - builder: (context, padding, child) { + valueListenable: widget.marginNotifier, + builder: (context, margin, child) { return Transform( alignment: Alignment.center, transform: imageToUserMatrix, @@ -106,12 +106,12 @@ class _EditorImageState extends State { builder: (context, action, child) { return LayoutBuilder( builder: (context, constraints) { - final viewportSize = padding.deflateSize(constraints.biggest); + final viewportSize = margin.deflateSize(constraints.biggest); final minScale = ScaleLevel(factor: ScaleLevel.scaleForContained(viewportSize, canvasSize)); return AvesMagnifier( key: Key('${entry.uri}_${entry.pageId}_${entry.dateModifiedSecs}'), controller: widget.magnifierController, - viewportPadding: padding, + viewportPadding: margin, contentSize: mediaSize, allowOriginalScaleBeyondRange: false, allowGestureScaleBeyondRange: false, diff --git a/lib/widgets/editor/transform/control_panel.dart b/lib/widgets/editor/transform/control_panel.dart index 5438de4a5..1dc1ccbae 100644 --- a/lib/widgets/editor/transform/control_panel.dart +++ b/lib/widgets/editor/transform/control_panel.dart @@ -81,8 +81,10 @@ class _TransformControlPanelState extends State with Tick const SizedBox(height: padding), Row( children: [ - const OverlayButton( - child: BackButton(), + OverlayButton( + child: BackButton( + onPressed: widget.onCancel, + ), ), Expanded( child: TabBar( diff --git a/lib/widgets/editor/transform/crop_region.dart b/lib/widgets/editor/transform/crop_region.dart index 013a12c9a..5a4e921aa 100644 --- a/lib/widgets/editor/transform/crop_region.dart +++ b/lib/widgets/editor/transform/crop_region.dart @@ -1,25 +1,17 @@ import 'dart:ui'; -import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/foundation.dart'; @immutable class CropRegion extends Equatable { + // region corners in image pixel coordinates final Offset topLeft, topRight, bottomRight, bottomLeft; List get corners => [topLeft, topRight, bottomRight, bottomLeft]; Offset get center => (topLeft + bottomRight) / 2; - Rect get outsideRect { - final xMin = corners.map((v) => v.dx).min; - final xMax = corners.map((v) => v.dx).max; - final yMin = corners.map((v) => v.dy).min; - final yMax = corners.map((v) => v.dy).max; - return Rect.fromPoints(Offset(xMin, yMin), Offset(xMax, yMax)); - } - @override List get props => [topLeft, topRight, bottomRight, bottomLeft]; @@ -30,7 +22,7 @@ class CropRegion extends Equatable { required this.bottomLeft, }); - static const CropRegion zero = CropRegion( + static const zero = CropRegion( topLeft: Offset.zero, topRight: Offset.zero, bottomRight: Offset.zero, diff --git a/lib/widgets/editor/transform/cropper.dart b/lib/widgets/editor/transform/cropper.dart index 310cb618e..0e7ee19fe 100644 --- a/lib/widgets/editor/transform/cropper.dart +++ b/lib/widgets/editor/transform/cropper.dart @@ -21,16 +21,16 @@ import 'package:provider/provider.dart'; class Cropper extends StatefulWidget { final AvesMagnifierController magnifierController; final TransformController transformController; - final ValueNotifier paddingNotifier; + final ValueNotifier marginNotifier; static const double handleDimension = kMinInteractiveDimension; - static const EdgeInsets imagePadding = EdgeInsets.all(kMinInteractiveDimension); + static const EdgeInsets imageMargin = EdgeInsets.all(kMinInteractiveDimension); const Cropper({ super.key, required this.magnifierController, required this.transformController, - required this.paddingNotifier, + required this.marginNotifier, }); @override @@ -39,7 +39,6 @@ class Cropper extends StatefulWidget { class _CropperState extends State with SingleTickerProviderStateMixin { final List _subscriptions = []; - final ValueNotifier _viewportSizeNotifier = ValueNotifier(Size.zero); final ValueNotifier _outlineNotifier = ValueNotifier(Rect.zero); final ValueNotifier _gridDivisionNotifier = ValueNotifier(0); late AnimationController _gridAnimationController; @@ -61,8 +60,6 @@ class _CropperState extends State with SingleTickerProviderStateMixin { @override void initState() { super.initState(); - final initialRegion = transformation.region; - _viewportSizeNotifier.addListener(() => _initOutline(initialRegion)); _gridAnimationController = AnimationController( duration: context.read().viewerOverlayAnimation, vsync: this, @@ -72,7 +69,6 @@ class _CropperState extends State with SingleTickerProviderStateMixin { curve: Curves.easeOutQuad, ); _registerWidget(widget); - _initOutline(initialRegion); } @override @@ -84,7 +80,6 @@ class _CropperState extends State with SingleTickerProviderStateMixin { @override void dispose() { - _viewportSizeNotifier.dispose(); _outlineNotifier.dispose(); _gridDivisionNotifier.dispose(); _gridOpacity.dispose(); @@ -95,7 +90,7 @@ class _CropperState extends State with SingleTickerProviderStateMixin { void _registerWidget(Cropper widget) { _subscriptions.add(widget.magnifierController.stateStream.listen(_onViewStateChanged)); - _subscriptions.add(widget.magnifierController.scaleBoundariesStream.listen(_onViewBoundariesChanged)); + _subscriptions.add(widget.magnifierController.scaleBoundariesStream.map((v) => v.viewportSize).listen(_onViewportSizeChanged)); _subscriptions.add(widget.transformController.eventStream.listen(_onTransformEvent)); _subscriptions.add(widget.transformController.transformationStream.map((v) => v.orientation).distinct().listen(_onOrientationChanged)); _subscriptions.add(widget.transformController.transformationStream.map((v) => v.straightenDegrees).distinct().listen(_onStraightenDegreesChanged)); @@ -113,14 +108,14 @@ class _CropperState extends State with SingleTickerProviderStateMixin { Widget build(BuildContext context) { return Positioned.fill( child: ValueListenableBuilder( - valueListenable: widget.paddingNotifier, - builder: (context, padding, child) { + valueListenable: widget.marginNotifier, + builder: (context, margin, child) { return ValueListenableBuilder( valueListenable: _outlineNotifier, builder: (context, outline, child) { if (outline.isEmpty) return const SizedBox(); - final outlineVisualRect = outline.translate(padding.left, padding.top); + final outlineVisualRect = outline.translate(margin.left, margin.top); return Stack( children: [ Positioned.fill( @@ -155,35 +150,35 @@ class _CropperState extends State with SingleTickerProviderStateMixin { ), ), _buildVertexHandle( - padding: padding, + margin: margin, getPosition: () => outline.topLeft, setPosition: (v) => _handleOutline( topLeft: Offset(min(outline.right - minDimension, v.dx), min(outline.bottom - minDimension, v.dy)), ), ), _buildVertexHandle( - padding: padding, + margin: margin, getPosition: () => outline.topRight, setPosition: (v) => _handleOutline( topRight: Offset(max(outline.left + minDimension, v.dx), min(outline.bottom - minDimension, v.dy)), ), ), _buildVertexHandle( - padding: padding, + margin: margin, getPosition: () => outline.bottomRight, setPosition: (v) => _handleOutline( bottomRight: Offset(max(outline.left + minDimension, v.dx), max(outline.top + minDimension, v.dy)), ), ), _buildVertexHandle( - padding: padding, + margin: margin, getPosition: () => outline.bottomLeft, setPosition: (v) => _handleOutline( bottomLeft: Offset(min(outline.right - minDimension, v.dx), max(outline.top + minDimension, v.dy)), ), ), _buildEdgeHandle( - padding: padding, + margin: margin, getEdge: () => Rect.fromPoints(outline.bottomLeft, outline.topLeft), setEdge: (v) { final left = min(outline.right - minDimension, v.left); @@ -194,7 +189,7 @@ class _CropperState extends State with SingleTickerProviderStateMixin { }, ), _buildEdgeHandle( - padding: padding, + margin: margin, getEdge: () => Rect.fromPoints(outline.topLeft, outline.topRight), setEdge: (v) { final top = min(outline.bottom - minDimension, v.top); @@ -205,7 +200,7 @@ class _CropperState extends State with SingleTickerProviderStateMixin { }, ), _buildEdgeHandle( - padding: padding, + margin: margin, getEdge: () => Rect.fromPoints(outline.bottomRight, outline.topRight), setEdge: (v) { final right = max(outline.left + minDimension, v.right); @@ -216,7 +211,7 @@ class _CropperState extends State with SingleTickerProviderStateMixin { }, ), _buildEdgeHandle( - padding: padding, + margin: margin, getEdge: () => Rect.fromPoints(outline.bottomLeft, outline.bottomRight), setEdge: (v) { final bottom = max(outline.top + minDimension, v.bottom); @@ -341,12 +336,12 @@ class _CropperState extends State with SingleTickerProviderStateMixin { } VertexHandle _buildVertexHandle({ - required EdgeInsets padding, + required EdgeInsets margin, required ValueGetter getPosition, required ValueSetter setPosition, }) { return VertexHandle( - padding: padding, + margin: margin, getPosition: getPosition, setPosition: setPosition, onDragStart: _onDragStart, @@ -355,12 +350,12 @@ class _CropperState extends State with SingleTickerProviderStateMixin { } EdgeHandle _buildEdgeHandle({ - required EdgeInsets padding, + required EdgeInsets margin, required ValueGetter getEdge, required ValueSetter setEdge, }) { return EdgeHandle( - padding: padding, + margin: margin, getEdge: getEdge, setEdge: setEdge, onDragStart: _onDragStart, @@ -392,11 +387,18 @@ class _CropperState extends State with SingleTickerProviderStateMixin { _setOutline(_regionToContainedOutline(nextState, region)); } - ViewState _viewStateForContainedRegion(ScaleBoundaries boundaries, CropRegion region) { - final regionSize = MatrixUtils.transformRect(transformation.matrix, region.outsideRect).size; - final nextScale = boundaries.clampScale(ScaleLevel.scaleForContained(boundaries.viewportSize, regionSize)); + ViewState _viewStateForContainedRegion(ScaleBoundaries boundaries, CropRegion imageRegion) { + final matrix = transformation.matrix; + final displayRegion = imageRegion.corners.map(matrix.transformOffset).toSet(); + final xMin = displayRegion.map((v) => v.dx).min; + final xMax = displayRegion.map((v) => v.dx).max; + final yMin = displayRegion.map((v) => v.dy).min; + final yMax = displayRegion.map((v) => v.dy).max; + final displayRegionSize = Size(xMax - xMin, yMax - yMin); + + final nextScale = boundaries.clampScale(ScaleLevel.scaleForContained(boundaries.viewportSize, displayRegionSize)); final nextPosition = boundaries.clampPosition( - position: boundaries.contentToStatePosition(nextScale, region.center), + position: boundaries.contentToStatePosition(nextScale, imageRegion.center), scale: nextScale, ); return ViewState( @@ -447,19 +449,30 @@ class _CropperState extends State with SingleTickerProviderStateMixin { void _onViewStateChanged(MagnifierState state) { final currentOutline = _outlineNotifier.value; - switch (state.source) { - case ChangeSource.internal: - case ChangeSource.animation: - _setOutline(currentOutline); - case ChangeSource.gesture: - // TODO TLAD [crop] use other strat - _setOutline(_applyCropRatioToOutline(currentOutline, _RatioStrategy.contain)); - _updateCropRegion(); - } + // switch (state.source) { + // case ChangeSource.internal: + // case ChangeSource.animation: + // _setOutline(currentOutline); + // case ChangeSource.gesture: + // TODO TLAD [crop] use other strat + _setOutline(_applyCropRatioToOutline(currentOutline, _RatioStrategy.contain)); + _updateCropRegion(); + // } } - void _onViewBoundariesChanged(ScaleBoundaries scaleBoundaries) { - _viewportSizeNotifier.value = scaleBoundaries.viewportSize; + void _onViewportSizeChanged(Size viewportSize) { + _initOutline(transformation.region); + + final boundaries = magnifierController.scaleBoundaries; + if (boundaries != null) { + final double xPadding = (viewportSize.width - minDimension) / 2; + final double yPadding = (viewportSize.height - minDimension) / 2; + magnifierController.setScaleBoundaries( + boundaries.copyWith( + padding: EdgeInsets.symmetric(horizontal: xPadding, vertical: yPadding), + ), + ); + } } ViewState? _getViewState() { diff --git a/lib/widgets/editor/transform/handles.dart b/lib/widgets/editor/transform/handles.dart index 9bcd87ad7..50884c4ea 100644 --- a/lib/widgets/editor/transform/handles.dart +++ b/lib/widgets/editor/transform/handles.dart @@ -2,14 +2,14 @@ import 'package:aves/widgets/editor/transform/cropper.dart'; import 'package:flutter/material.dart'; class VertexHandle extends StatefulWidget { - final EdgeInsets padding; + final EdgeInsets margin; final ValueGetter getPosition; final ValueSetter setPosition; final VoidCallback onDragStart, onDragEnd; const VertexHandle({ super.key, - required this.padding, + required this.margin, required this.getPosition, required this.setPosition, required this.onDragStart, @@ -26,13 +26,13 @@ class _VertexHandleState extends State { static const double _handleDim = Cropper.handleDimension; - EdgeInsets get padding => widget.padding; + EdgeInsets get margin => widget.margin; @override Widget build(BuildContext context) { return Positioned.fromRect( rect: Rect.fromCenter( - center: widget.getPosition().translate(padding.left, padding.right), + center: widget.getPosition().translate(margin.left, margin.right), width: _handleDim, height: _handleDim, ), @@ -58,14 +58,14 @@ class _VertexHandleState extends State { } class EdgeHandle extends StatefulWidget { - final EdgeInsets padding; + final EdgeInsets margin; final ValueGetter getEdge; final ValueSetter setEdge; final VoidCallback onDragStart, onDragEnd; const EdgeHandle({ super.key, - required this.padding, + required this.margin, required this.getEdge, required this.setEdge, required this.onDragStart, @@ -82,7 +82,7 @@ class _EdgeHandleState extends State { static const double _handleDim = Cropper.handleDimension; - EdgeInsets get padding => widget.padding; + EdgeInsets get margin => widget.margin; @override Widget build(BuildContext context) { @@ -94,7 +94,7 @@ class _EdgeHandleState extends State { // vertical edge edge = Rect.fromLTWH(edge.left - _handleDim / 2, edge.top + _handleDim / 2, _handleDim, edge.height - _handleDim); } - edge = edge.translate(padding.left, padding.right); + edge = edge.translate(margin.left, margin.right); return Positioned.fromRect( rect: edge, diff --git a/plugins/aves_magnifier/lib/src/controller/controller.dart b/plugins/aves_magnifier/lib/src/controller/controller.dart index 240e44214..d2d3695af 100644 --- a/plugins/aves_magnifier/lib/src/controller/controller.dart +++ b/plugins/aves_magnifier/lib/src/controller/controller.dart @@ -32,7 +32,7 @@ class AvesMagnifierController { initial = initialState ?? const MagnifierState(position: Offset.zero, scale: null, source: source); previousState = initial; _currentState = initial; - _setState(initial); + reset(); const _initialScaleState = ScaleStateChange(state: ScaleState.initial, source: source); previousScaleState = _initialScaleState; @@ -70,6 +70,8 @@ class AvesMagnifierController { bool get isZooming => scaleState.state == ScaleState.zoomedIn || scaleState.state == ScaleState.zoomedOut; + void reset() => _setState(initial); + void update({ Offset? position, double? scale, diff --git a/plugins/aves_magnifier/lib/src/controller/controller_delegate.dart b/plugins/aves_magnifier/lib/src/controller/controller_delegate.dart index 8ab9907de..4b65110c4 100644 --- a/plugins/aves_magnifier/lib/src/controller/controller_delegate.dart +++ b/plugins/aves_magnifier/lib/src/controller/controller_delegate.dart @@ -21,14 +21,12 @@ mixin AvesMagnifierControllerDelegate on State { Function(double? prevScale, double? nextScale, Offset nextPosition)? _animateScale; - /// Mark if scale need recalculation, useful for scale boundaries changes. - bool markNeedsScaleRecalc = true; - final List _subscriptions = []; void registerDelegate(AvesMagnifier widget) { _subscriptions.add(widget.controller.stateStream.listen(_onMagnifierStateChanged)); _subscriptions.add(widget.controller.scaleStateChangeStream.listen(_onScaleStateChanged)); + _subscriptions.add(widget.controller.scaleBoundariesStream.listen(_onScaleBoundariesChanged)); } void unregisterDelegate(AvesMagnifier oldWidget) { @@ -38,12 +36,16 @@ mixin AvesMagnifierControllerDelegate on State { ..clear(); } + void _onScaleBoundariesChanged(ScaleBoundaries boundaries) { + initScale(); + } + 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); + controller.update(scale: controller.scale, source: scaleStateChange.source); return; } @@ -68,34 +70,24 @@ mixin AvesMagnifierControllerDelegate on State { void _onMagnifierStateChanged(MagnifierState state) { final boundaries = scaleBoundaries; - if (boundaries == null) return; + final currentScale = controller.scale; + if (boundaries == null || currentScale == null) return; - controller.update(position: boundaries.clampPosition(position: position, scale: scale!), source: state.source); - if (controller.scale == controller.previousState.scale) return; + controller.update(position: boundaries.clampPosition(position: position, scale: currentScale), source: state.source); + final newScale = controller.scale; + if (newScale == null || newScale == currentScale) return; if (state.source == ChangeSource.internal || state.source == ChangeSource.animation) return; - final newScaleState = (scale! > boundaries.initialScale) ? ScaleState.zoomedIn : ScaleState.zoomedOut; + final newScaleState = (newScale > boundaries.initialScale) ? ScaleState.zoomedIn : ScaleState.zoomedOut; controller.setScaleState(newScaleState, state.source); } Offset get position => controller.position; - double? recalcScale() { + void initScale() { 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); @@ -105,7 +97,7 @@ mixin AvesMagnifierControllerDelegate on State { if (boundaries == null) return; var newScaleState = ScaleState.initial; - if (scale != boundaries.initialScale) { + if (controller.scale != boundaries.initialScale) { newScaleState = (newScale > boundaries.initialScale) ? ScaleState.zoomedIn : ScaleState.zoomedOut; } controller.setScaleState(newScaleState, source); diff --git a/plugins/aves_magnifier/lib/src/core/core.dart b/plugins/aves_magnifier/lib/src/core/core.dart index 395a6234d..c40179436 100644 --- a/plugins/aves_magnifier/lib/src/core/core.dart +++ b/plugins/aves_magnifier/lib/src/core/core.dart @@ -97,8 +97,6 @@ class _AvesMagnifierState extends State with TickerProviderStateM late AnimationController _positionAnimationController; late Animation _positionAnimation; - ScaleBoundaries? cachedScaleBoundaries; - static const _flingPointerKind = PointerDeviceKind.unknown; @override @@ -111,7 +109,7 @@ class _AvesMagnifierState extends State with TickerProviderStateM _registerWidget(widget); // force delegate scale computing on initialization // so that it does not happen lazily at the beginning of a scale animation - recalcScale(); + initScale(); } @override @@ -144,13 +142,11 @@ class _AvesMagnifierState extends State with TickerProviderStateM void _registerWidget(AvesMagnifier widget) { registerDelegate(widget); - cachedScaleBoundaries = widget.controller.scaleBoundaries; setScaleStateUpdateAnimation(animateOnScaleStateUpdate); } void _unregisterWidget(AvesMagnifier oldWidget) { unregisterDelegate(oldWidget); - cachedScaleBoundaries = null; } void handleScaleAnimation() { @@ -176,7 +172,7 @@ class _AvesMagnifierState extends State with TickerProviderStateM _mayFlingLTRB = const (true, true, true, true); _updateMayFling(); - _startScale = scale; + _startScale = controller.scale; _startFocalPoint = details.localFocalPoint; _lastViewportFocalPosition = _startFocalPoint; _dropped = false; @@ -214,7 +210,7 @@ class _AvesMagnifierState extends State with TickerProviderStateM final factor = _quickScaleMoved ? (focalPointY > _quickScaleLastY! ? (1 + spanDiff) : (1 - spanDiff)) : 1; _quickScaleLastDistance = distance; _quickScaleLastY = focalPointY; - newScale = scale! * factor; + newScale = controller.scale! * factor; } else { newScale = _startScale! * details.scale; } @@ -227,7 +223,7 @@ class _AvesMagnifierState extends State with TickerProviderStateM final viewportCenter = boundaries.viewportCenter; final centerContentPosition = boundaries.viewportToContentPosition(controller, viewportCenter); - final scalePositionDelta = (scaleFocalPoint - viewportCenter) * (scale! / newScale - 1); + final scalePositionDelta = (scaleFocalPoint - viewportCenter) * (controller.scale! / newScale - 1); final panPositionDelta = scaleFocalPoint - _lastViewportFocalPosition!; final newPosition = boundaries.clampPosition( @@ -434,7 +430,7 @@ class _AvesMagnifierState extends State with TickerProviderStateM /// Check if scale is equal to initial after scale animation update void onAnimationStatusCompleted() { - if (controller.scaleState.state != ScaleState.initial && scale == scaleBoundaries?.initialScale) { + if (controller.scaleState.state != ScaleState.initial && controller.scale == scaleBoundaries?.initialScale) { controller.setScaleState(ScaleState.initial, ChangeSource.animation); } } @@ -446,12 +442,6 @@ class _AvesMagnifierState extends State with TickerProviderStateM @override Widget build(BuildContext context) { - // Check if we need a recalc on the scale - if (widget.controller.scaleBoundaries != cachedScaleBoundaries) { - markNeedsScaleRecalc = true; - cachedScaleBoundaries = widget.controller.scaleBoundaries; - } - return StreamBuilder( stream: controller.stateStream, initialData: controller.previousState, @@ -494,7 +484,7 @@ class _AvesMagnifierState extends State with TickerProviderStateM controller.setScaleBoundaries(boundaries); // `Matrix4.scale` uses dynamic typing and can throw `UnimplementedError` on wrong types - final double effectiveScale = (applyScale ? scale : null) ?? 1.0; + final double effectiveScale = (applyScale ? controller.scale : null) ?? 1.0; return Transform( transform: Matrix4.identity() ..translate(position.dx, position.dy) diff --git a/plugins/aves_magnifier/lib/src/pan/edge_hit_detector.dart b/plugins/aves_magnifier/lib/src/pan/edge_hit_detector.dart index 418e7d790..9aa0329fa 100644 --- a/plugins/aves_magnifier/lib/src/pan/edge_hit_detector.dart +++ b/plugins/aves_magnifier/lib/src/pan/edge_hit_detector.dart @@ -1,6 +1,5 @@ import 'package:aves_magnifier/src/controller/controller_delegate.dart'; import 'package:equatable/equatable.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; mixin EdgeHitDetector on AvesMagnifierControllerDelegate { @@ -11,14 +10,9 @@ mixin EdgeHitDetector on AvesMagnifierControllerDelegate { EdgeHit getXEdgeHit() { final _boundaries = scaleBoundaries; - final _scale = scale; + final _scale = controller.scale; if (_boundaries == null || _scale == null) return const EdgeHit(false, false); - final contentWidth = _boundaries.contentSize.width * _scale; - final viewportWidth = _boundaries.viewportSize.width; - if (viewportWidth + precisionErrorTolerance >= contentWidth) { - return const EdgeHit(true, true); - } final x = -position.dx; final range = _boundaries.getXEdges(scale: _scale); return EdgeHit(x <= range.min, x >= range.max); @@ -26,14 +20,9 @@ mixin EdgeHitDetector on AvesMagnifierControllerDelegate { EdgeHit getYEdgeHit() { final _boundaries = scaleBoundaries; - final _scale = scale; + final _scale = controller.scale; if (_boundaries == null || _scale == null) return const EdgeHit(false, false); - final contentHeight = _boundaries.contentSize.height * _scale; - final viewportHeight = _boundaries.viewportSize.height; - if (viewportHeight + precisionErrorTolerance >= contentHeight) { - return const EdgeHit(true, true); - } final y = -position.dy; final range = _boundaries.getYEdges(scale: _scale); return EdgeHit(y <= range.min, y >= range.max); diff --git a/plugins/aves_magnifier/lib/src/scale/scale_boundaries.dart b/plugins/aves_magnifier/lib/src/scale/scale_boundaries.dart index 9329b652c..a7bf5b439 100644 --- a/plugins/aves_magnifier/lib/src/scale/scale_boundaries.dart +++ b/plugins/aves_magnifier/lib/src/scale/scale_boundaries.dart @@ -16,12 +16,13 @@ class ScaleBoundaries extends Equatable { final ScaleLevel _initialScale; final Size viewportSize; final Size contentSize; + final EdgeInsets padding; final Matrix4? externalTransform; static const Alignment basePosition = Alignment.center; @override - List get props => [_allowOriginalScaleBeyondRange, _minScale, _maxScale, _initialScale, viewportSize, contentSize, externalTransform]; + List get props => [_allowOriginalScaleBeyondRange, _minScale, _maxScale, _initialScale, viewportSize, contentSize, padding, externalTransform]; const ScaleBoundaries({ required bool allowOriginalScaleBeyondRange, @@ -30,6 +31,7 @@ class ScaleBoundaries extends Equatable { required ScaleLevel initialScale, required this.viewportSize, required this.contentSize, + this.padding = EdgeInsets.zero, this.externalTransform, }) : _allowOriginalScaleBeyondRange = allowOriginalScaleBeyondRange, _minScale = minScale, @@ -43,6 +45,7 @@ class ScaleBoundaries extends Equatable { initialScale: ScaleLevel(ref: ScaleReference.contained), viewportSize: Size.zero, contentSize: Size.zero, + padding: EdgeInsets.zero, ); ScaleBoundaries copyWith({ @@ -52,6 +55,7 @@ class ScaleBoundaries extends Equatable { ScaleLevel? initialScale, Size? viewportSize, Size? contentSize, + EdgeInsets? padding, Matrix4? externalTransform, }) { return ScaleBoundaries( @@ -61,6 +65,7 @@ class ScaleBoundaries extends Equatable { initialScale: initialScale ?? _initialScale, viewportSize: viewportSize ?? this.viewportSize, contentSize: contentSize ?? this.contentSize, + padding: padding ?? this.padding, externalTransform: externalTransform ?? this.externalTransform, ); } @@ -110,7 +115,7 @@ class ScaleBoundaries extends Equatable { final minX = ((positionX - 1).abs() / 2) * widthDiff * -1; final maxX = ((positionX + 1).abs() / 2) * widthDiff; - return EdgeRange(minX, maxX); + return EdgeRange(minX - padding.left, maxX + padding.right); } EdgeRange getYEdges({required double scale}) { @@ -122,7 +127,7 @@ class ScaleBoundaries extends Equatable { final minY = ((positionY - 1).abs() / 2) * heightDiff * -1; final maxY = ((positionY + 1).abs() / 2) * heightDiff; - return EdgeRange(minY, maxY); + return EdgeRange(minY - padding.top, maxY + padding.bottom); } double clampScale(double scale) { @@ -142,24 +147,11 @@ class ScaleBoundaries extends Equatable { } Offset clampPosition({required Offset position, required double scale}) { - final computedWidth = contentSize.width * scale; - final computedHeight = contentSize.height * scale; - - final viewportWidth = _transformedViewportSize.width; - final viewportHeight = _transformedViewportSize.height; - - var finalX = 0.0; - if (viewportWidth < computedWidth) { - final range = getXEdges(scale: scale); - finalX = position.dx.clamp(range.min, range.max); - } - - var finalY = 0.0; - if (viewportHeight < computedHeight) { - final range = getYEdges(scale: scale); - finalY = position.dy.clamp(range.min, range.max); - } - - return Offset(finalX, finalY); + 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), + ); } }