feat: çift tıklama fill/fit toggle + ok tuşları ile gezinme
Build Windows / build-windows (push) Has been cancelled
Build Windows / build-windows (push) Has been cancelled
This commit is contained in:
1 parent
f267442fdb
commit
31b1032661
5 files changed
+62
-3
No files matched your search
Binary file not shown.
Binary file not shown.
@@ -14,12 +14,30 @@ class ImageCanvas extends StatefulWidget {
|
|||||||
class _ImageCanvasState extends State<ImageCanvas> {
|
class _ImageCanvasState extends State<ImageCanvas> {
|
||||||
late TransformationController _controller;
|
late TransformationController _controller;
|
||||||
double _currentScale = 1.0;
|
double _currentScale = 1.0;
|
||||||
|
bool _isFilled = false; // false = fit, true = fill screen
|
||||||
|
Size? _imageSize;
|
||||||
|
Size? _viewportSize;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_controller = TransformationController();
|
_controller = TransformationController();
|
||||||
_controller.addListener(_onTransformChanged);
|
_controller.addListener(_onTransformChanged);
|
||||||
|
_loadImageSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadImageSize() {
|
||||||
|
final image = Image.file(File(widget.filePath));
|
||||||
|
image.image.resolve(const ImageConfiguration()).addListener(
|
||||||
|
ImageStreamListener((info, _) {
|
||||||
|
setState(() {
|
||||||
|
_imageSize = Size(
|
||||||
|
info.image.width.toDouble(),
|
||||||
|
info.image.height.toDouble(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -27,22 +45,58 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
|||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (oldWidget.filePath != widget.filePath) {
|
if (oldWidget.filePath != widget.filePath) {
|
||||||
_resetView();
|
_resetView();
|
||||||
|
_loadImageSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _resetView() {
|
void _resetView() {
|
||||||
_controller.value = Matrix4.identity();
|
_controller.value = Matrix4.identity();
|
||||||
_currentScale = 1.0;
|
_currentScale = 1.0;
|
||||||
|
_isFilled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onTransformChanged() {
|
void _onTransformChanged() {
|
||||||
_currentScale = _controller.value.getMaxScaleOnAxis();
|
_currentScale = _controller.value.getMaxScaleOnAxis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _toggleFillFit() {
|
||||||
|
if (_imageSize == null || _viewportSize == null) return;
|
||||||
|
|
||||||
|
if (_isFilled) {
|
||||||
|
// Go to fit mode — reset transform
|
||||||
|
_controller.value = Matrix4.identity();
|
||||||
|
_currentScale = 1.0;
|
||||||
|
_isFilled = false;
|
||||||
|
} else {
|
||||||
|
// Go to fill mode — scale to cover viewport
|
||||||
|
final scaleX = _viewportSize!.width / _imageSize!.width;
|
||||||
|
final scaleY = _viewportSize!.height / _imageSize!.height;
|
||||||
|
final fillScale = scaleX > scaleY ? scaleX : scaleY;
|
||||||
|
|
||||||
|
// Center the image
|
||||||
|
final offsetX = (_viewportSize!.width - _imageSize!.width * fillScale) / 2;
|
||||||
|
final offsetY = (_viewportSize!.height - _imageSize!.height * fillScale) / 2;
|
||||||
|
|
||||||
|
_controller.value = Matrix4.identity()
|
||||||
|
// ignore: deprecated_member_use
|
||||||
|
..translate(offsetX, offsetY)
|
||||||
|
// ignore: deprecated_member_use
|
||||||
|
..scale(fillScale);
|
||||||
|
_currentScale = fillScale;
|
||||||
|
_isFilled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleDoubleTap() {
|
||||||
|
_toggleFillFit();
|
||||||
|
}
|
||||||
|
|
||||||
void _handleScroll(PointerScrollEvent event) {
|
void _handleScroll(PointerScrollEvent event) {
|
||||||
final renderBox = context.findRenderObject() as RenderBox?;
|
final renderBox = context.findRenderObject() as RenderBox?;
|
||||||
if (renderBox == null) return;
|
if (renderBox == null) return;
|
||||||
|
|
||||||
|
_viewportSize = renderBox.size;
|
||||||
|
|
||||||
final localPosition = renderBox.globalToLocal(event.position);
|
final localPosition = renderBox.globalToLocal(event.position);
|
||||||
|
|
||||||
final delta = event.scrollDelta.dy;
|
final delta = event.scrollDelta.dy;
|
||||||
@@ -53,8 +107,6 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
|||||||
|
|
||||||
final actualScale = newScale / _currentScale;
|
final actualScale = newScale / _currentScale;
|
||||||
|
|
||||||
// Decompose current matrix: widget_point = scale * child_point + translation
|
|
||||||
// So: child_point = (widget_point - translation) / scale
|
|
||||||
final matrix = _controller.value;
|
final matrix = _controller.value;
|
||||||
final tx = matrix.getTranslation().x;
|
final tx = matrix.getTranslation().x;
|
||||||
final ty = matrix.getTranslation().y;
|
final ty = matrix.getTranslation().y;
|
||||||
@@ -62,7 +114,6 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
|||||||
final childX = (localPosition.dx - tx) / _currentScale;
|
final childX = (localPosition.dx - tx) / _currentScale;
|
||||||
final childY = (localPosition.dy - ty) / _currentScale;
|
final childY = (localPosition.dy - ty) / _currentScale;
|
||||||
|
|
||||||
// Build new matrix: zoom around child_point, then re-apply
|
|
||||||
final newMatrix = Matrix4.identity()
|
final newMatrix = Matrix4.identity()
|
||||||
// ignore: deprecated_member_use
|
// ignore: deprecated_member_use
|
||||||
..translate(childX, childY)
|
..translate(childX, childY)
|
||||||
@@ -84,12 +135,17 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
_viewportSize = Size(constraints.maxWidth, constraints.maxHeight);
|
||||||
return Listener(
|
return Listener(
|
||||||
onPointerSignal: (event) {
|
onPointerSignal: (event) {
|
||||||
if (event is PointerScrollEvent) {
|
if (event is PointerScrollEvent) {
|
||||||
_handleScroll(event);
|
_handleScroll(event);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
child: GestureDetector(
|
||||||
|
onDoubleTap: _handleDoubleTap,
|
||||||
child: InteractiveViewer(
|
child: InteractiveViewer(
|
||||||
transformationController: _controller,
|
transformationController: _controller,
|
||||||
boundaryMargin: const EdgeInsets.all(double.infinity),
|
boundaryMargin: const EdgeInsets.all(double.infinity),
|
||||||
@@ -120,6 +176,9 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user