editor: keep area on pan to edge
This commit is contained in:
parent
b89db3bc9b
commit
95e0272afb
4 changed files with 51 additions and 46 deletions
|
@ -117,13 +117,13 @@ class _ImageEditorPageState extends State<ImageEditorPage> {
|
|||
);
|
||||
}
|
||||
|
||||
void _onActionChanged() => _updateImageMargin();
|
||||
|
||||
void _updateImageMargin() {
|
||||
if (_actionNotifier.value == EditorAction.transform) {
|
||||
_marginNotifier.value = Cropper.imageMargin;
|
||||
} else {
|
||||
_marginNotifier.value = EdgeInsets.zero;
|
||||
void _onActionChanged() {
|
||||
switch(_actionNotifier.value) {
|
||||
case EditorAction.transform:
|
||||
_transformController.reset();
|
||||
_marginNotifier.value = Cropper.imageMargin;
|
||||
default:
|
||||
_marginNotifier.value = EdgeInsets.zero;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ class _TransformControlPanelState extends State<TransformControlPanel> with Tick
|
|||
OverlayButton(
|
||||
child: StreamBuilder<Transformation>(
|
||||
stream: transformController.transformationStream,
|
||||
builder: (context, snapshot) {
|
||||
builder: (context, _) {
|
||||
return IconButton(
|
||||
icon: const Icon(AIcons.apply),
|
||||
onPressed: transformController.modified ? () => widget.onApply(transformController.transformation) : null,
|
||||
|
@ -122,17 +122,17 @@ class CropControlPanel extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final aspectRatioNotifier = context.select<TransformController, ValueNotifier<CropAspectRatio>>((v) => v.aspectRatioNotifier);
|
||||
final controller = context.watch<TransformController>();
|
||||
return ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
final ratio = CropAspectRatio.values[index];
|
||||
void setAspectRatio() => aspectRatioNotifier.value = ratio;
|
||||
void setAspectRatio() => controller.setAspectRatio(ratio);
|
||||
return CaptionedButton(
|
||||
iconButtonBuilder: (context, focusNode) {
|
||||
return ValueListenableBuilder<CropAspectRatio>(
|
||||
valueListenable: aspectRatioNotifier,
|
||||
valueListenable: controller.aspectRatioNotifier,
|
||||
builder: (context, selectedRatio, child) {
|
||||
return IconButton(
|
||||
color: ratio == selectedRatio ? Theme.of(context).colorScheme.primary : null,
|
||||
|
@ -166,17 +166,21 @@ class RotationControlPanel extends StatelessWidget {
|
|||
Expanded(
|
||||
child: StreamBuilder<Transformation>(
|
||||
stream: controller.transformationStream,
|
||||
builder: (context, snapshot) {
|
||||
final transformation = snapshot.data ?? Transformation.zero;
|
||||
return Slider(
|
||||
value: transformation.straightenDegrees,
|
||||
min: TransformController.straightenDegreesMin,
|
||||
max: TransformController.straightenDegreesMax,
|
||||
divisions: 18,
|
||||
onChangeStart: (v) => controller.activity = TransformActivity.straighten,
|
||||
onChangeEnd: (v) => controller.activity = TransformActivity.none,
|
||||
label: angleFormatter.format(transformation.straightenDegrees),
|
||||
onChanged: (v) => controller.straightenDegrees = v,
|
||||
builder: (context, _) {
|
||||
final degrees = controller.transformation.straightenDegrees;
|
||||
return SliderTheme(
|
||||
data: SliderTheme.of(context).copyWith(
|
||||
showValueIndicator: ShowValueIndicator.always,
|
||||
),
|
||||
child: Slider(
|
||||
value: degrees,
|
||||
min: TransformController.straightenDegreesMin,
|
||||
max: TransformController.straightenDegreesMax,
|
||||
onChangeStart: (v) => controller.activity = TransformActivity.straighten,
|
||||
onChangeEnd: (v) => controller.activity = TransformActivity.none,
|
||||
label: angleFormatter.format(degrees),
|
||||
onChanged: (v) => controller.straightenDegrees = v,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -57,6 +57,7 @@ class TransformController {
|
|||
region: CropRegion.fromRect(Offset.zero & displaySize),
|
||||
);
|
||||
_transformationStreamController.add(_transformation);
|
||||
setAspectRatio(CropAspectRatio.free);
|
||||
}
|
||||
|
||||
void flipHorizontally() {
|
||||
|
@ -93,6 +94,8 @@ class TransformController {
|
|||
_activityStreamController.add(_activity);
|
||||
}
|
||||
|
||||
void setAspectRatio(CropAspectRatio ratio) => aspectRatioNotifier.value = ratio;
|
||||
|
||||
void _onAspectRatioChanged() {
|
||||
// TODO TLAD [crop] apply
|
||||
}
|
||||
|
|
|
@ -437,20 +437,12 @@ class _CropperState extends State<Cropper> with SingleTickerProviderStateMixin {
|
|||
}
|
||||
|
||||
void _onViewStateChanged(MagnifierState state) {
|
||||
final currentOutline = _outlineNotifier.value;
|
||||
|
||||
// TODO TLAD [crop] use other strat
|
||||
|
||||
// switch (state.source) {
|
||||
// case ChangeSource.internal:
|
||||
// case ChangeSource.animation:
|
||||
// case ChangeSource.gesture:
|
||||
// }
|
||||
|
||||
switch (transformController.activity) {
|
||||
case TransformActivity.none:
|
||||
break;
|
||||
case TransformActivity.straighten:
|
||||
case TransformActivity.pan:
|
||||
final currentOutline = _outlineNotifier.value;
|
||||
_setOutline(_applyCropRatioToOutline(currentOutline, _RatioStrategy.contain));
|
||||
case TransformActivity.resize:
|
||||
break;
|
||||
|
@ -488,8 +480,26 @@ class _CropperState extends State<Cropper> with SingleTickerProviderStateMixin {
|
|||
if (targetOutline.isEmpty || viewState == null || viewportSize == null) return;
|
||||
|
||||
// ensure outline is within content
|
||||
final targetRegion = _regionFromOutline(viewState, targetOutline);
|
||||
var targetRegion = _regionFromOutline(viewState, targetOutline);
|
||||
var newOutline = _containedOutlineFromRegion(viewState, targetRegion);
|
||||
final outlineWidthDelta = targetOutline.width - newOutline.width;
|
||||
final outlineHeightDelta = targetOutline.height - newOutline.height;
|
||||
if (outlineWidthDelta > precisionErrorTolerance || outlineHeightDelta > precisionErrorTolerance) {
|
||||
// keep outline area if possible, otherwise trim
|
||||
final regionToOutlineMatrix = _getRegionToOutlineMatrix(viewState);
|
||||
final rect = Offset.zero & viewState.contentSize!;
|
||||
final edgeRegionCorners = targetRegion.corners.where((v) => v.dx == rect.left || v.dx == rect.right || v.dy == rect.top || v.dy == rect.bottom).toSet();
|
||||
final edgeOutlineCorners = edgeRegionCorners.map(regionToOutlineMatrix.transformOffset).toSet();
|
||||
if (edgeOutlineCorners.isNotEmpty) {
|
||||
final direction = edgeOutlineCorners.map((v) => newOutline.center - v).reduce((prev, v) => prev + v);
|
||||
final movedOutline = targetOutline.shift(Offset(
|
||||
outlineWidthDelta * direction.dx.sign,
|
||||
outlineHeightDelta * direction.dy.sign,
|
||||
));
|
||||
targetRegion = _regionFromOutline(viewState, movedOutline);
|
||||
newOutline = _containedOutlineFromRegion(viewState, targetRegion);
|
||||
}
|
||||
}
|
||||
|
||||
// ensure outline is large enough to be handled
|
||||
newOutline = Rect.fromLTWH(
|
||||
|
@ -499,25 +509,13 @@ class _CropperState extends State<Cropper> with SingleTickerProviderStateMixin {
|
|||
max(newOutline.height, minDimension),
|
||||
);
|
||||
|
||||
// ensure outline is within viewport
|
||||
newOutline = Rect.fromLTRB(
|
||||
max(newOutline.left, 0),
|
||||
max(newOutline.top, 0),
|
||||
min(newOutline.right, viewportSize.width),
|
||||
min(newOutline.bottom, viewportSize.height),
|
||||
);
|
||||
|
||||
final oldOutline = _outlineNotifier.value;
|
||||
_outlineNotifier.value = newOutline;
|
||||
switch (transformController.activity) {
|
||||
case TransformActivity.none:
|
||||
if (oldOutline.isEmpty) {
|
||||
_updateCropRegion();
|
||||
}
|
||||
case TransformActivity.pan:
|
||||
case TransformActivity.resize:
|
||||
_updateCropRegion();
|
||||
break;
|
||||
case TransformActivity.none:
|
||||
case TransformActivity.straighten:
|
||||
break;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue