aves/lib/widgets/editor/entry_editor_page.dart
Thibault Deckers d11bd21d89 magnifier: scale boundaries padding;
editor: pan fixes
2025-02-08 19:32:35 +01:00

140 lines
4.8 KiB
Dart

import 'dart:async';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/viewer/view_state.dart';
import 'package:aves/widgets/editor/control_panel.dart';
import 'package:aves/widgets/editor/image.dart';
import 'package:aves/widgets/editor/transform/controller.dart';
import 'package:aves/widgets/editor/transform/cropper.dart';
import 'package:aves/widgets/viewer/overlay/minimap.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';
class ImageEditorPage extends StatefulWidget {
static const routeName = '/image_editor';
final AvesEntry entry;
const ImageEditorPage({
super.key,
required this.entry,
});
@override
State<ImageEditorPage> createState() => _ImageEditorPageState();
}
class _ImageEditorPageState extends State<ImageEditorPage> {
final List<StreamSubscription> _subscriptions = [];
final ValueNotifier<EditorAction?> _actionNotifier = ValueNotifier(null);
final ValueNotifier<EdgeInsets> _marginNotifier = ValueNotifier(EdgeInsets.zero);
final ValueNotifier<ViewState> _viewStateNotifier = ValueNotifier<ViewState>(ViewState.zero);
final AvesMagnifierController _magnifierController = AvesMagnifierController();
late final TransformController _transformController;
@override
void initState() {
super.initState();
_transformController = TransformController(widget.entry.displaySize);
_actionNotifier.addListener(_onActionChanged);
_subscriptions.add(_transformController.transformationStream.map((v) => v.matrix).distinct().listen(_onTransformationMatrixChanged));
}
@override
void dispose() {
_subscriptions
..forEach((sub) => sub.cancel())
..clear();
_actionNotifier.dispose();
_marginNotifier.dispose();
_viewStateNotifier.dispose();
_magnifierController.dispose();
_transformController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: MultiProvider(
providers: [
Provider<AvesMagnifierController>.value(value: _magnifierController),
Provider<TransformController>.value(value: _transformController),
],
child: SafeArea(
child: Column(
children: [
Expanded(
child: Stack(
children: [
ClipRect(
child: EditorImage(
magnifierController: _magnifierController,
transformController: _transformController,
actionNotifier: _actionNotifier,
marginNotifier: _marginNotifier,
viewStateNotifier: _viewStateNotifier,
entry: widget.entry,
),
),
if (settings.showOverlayMinimap)
PositionedDirectional(
start: 8,
bottom: 8,
child: Minimap(viewStateNotifier: _viewStateNotifier),
),
ValueListenableBuilder<EditorAction?>(
valueListenable: _actionNotifier,
builder: (context, action, child) {
switch (action) {
case EditorAction.transform:
return Cropper(
magnifierController: _magnifierController,
transformController: _transformController,
marginNotifier: _marginNotifier,
);
case null:
return const SizedBox();
}
},
),
],
),
),
const Divider(height: 0),
EditorControlPanel(
entry: widget.entry,
actionNotifier: _actionNotifier,
),
],
),
),
),
resizeToAvoidBottomInset: false,
);
}
void _onActionChanged() => _updateImageMargin();
void _updateImageMargin() {
if (_actionNotifier.value == EditorAction.transform) {
_marginNotifier.value = Cropper.imageMargin;
} else {
_marginNotifier.value = EdgeInsets.zero;
}
}
void _onTransformationMatrixChanged(Matrix4 transformationMatrix) {
final boundaries = _magnifierController.scaleBoundaries;
if (boundaries != null) {
_magnifierController.setScaleBoundaries(
boundaries.copyWith(
externalTransform: transformationMatrix,
),
);
}
}
}