Skip to content

Commit

Permalink
feat(zoom): add zoom functionality to Paint-Editor and Main-Editor
Browse files Browse the repository at this point in the history
  • Loading branch information
hm21 committed Jun 24, 2024
1 parent 74982d6 commit 721f072
Show file tree
Hide file tree
Showing 26 changed files with 863 additions and 183 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## Version 4.1.0
- **feat(zoom)**: Paint-Editor and Main-Editor are now zoomable. An example of how to enable this can be found [here](https://github.com/hm21/pro_image_editor/blob/74982d6c8e3e3d2d16e8f77821f9bcd839863c23/example/lib/pages/zoom_move_editor_example.dart)

## Version 4.0.10
- **feat(text-editor)**: Add autocorrect and enableSuggestions configs. This was requsted in [#132](https://github.com/hm21/pro_image_editor/issues/132)
- **fix(text-editor)**: Remove duplicate text-shadow from invisible text-field. This resolves issue [#131](https://github.com/hm21/pro_image_editor/issue/131).
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ The ProImageEditor is a Flutter widget designed for image editing within your ap
- ✅ Interactive layers
- ✅ Helper lines for better positioning
- ✅ Hit detection for painted layers
- ✅ Zoomable paint and main editor
- ✅ Improved layer movement and scaling functionality for desktop devices


Expand Down
2 changes: 2 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Flutter imports:
import 'package:example/pages/design_examples/design_example.dart';
import 'package:example/pages/zoom_move_editor_example.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Expand Down Expand Up @@ -82,6 +83,7 @@ class _MyHomePageState extends State<MyHomePage> {
const CustomAppbarBottombarExample(),
const ImportExportExample(),
const MoveableBackgroundImageExample(),
const ZoomMoveEditorExample(),
const ImageFormatConvertExample(),
];

Expand Down
71 changes: 71 additions & 0 deletions example/lib/pages/zoom_move_editor_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Flutter imports:
import 'package:flutter/material.dart';
import 'package:pro_image_editor/models/editor_configs/main_editor_configs.dart';

// Package imports:
import 'package:pro_image_editor/pro_image_editor.dart';

// Project imports:
import '../utils/example_constants.dart';
import '../utils/example_helper.dart';

class ZoomMoveEditorExample extends StatefulWidget {
const ZoomMoveEditorExample({super.key});

@override
State<ZoomMoveEditorExample> createState() => _ZoomMoveEditorExampleState();
}

class _ZoomMoveEditorExampleState extends State<ZoomMoveEditorExample>
with ExampleHelperState<ZoomMoveEditorExample> {
@override
Widget build(BuildContext context) {
return ListTile(
onTap: () async {
await precacheImage(
AssetImage(ExampleConstants.of(context)!.demoAssetPath), context);
if (!context.mounted) return;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => _buildEditor(),
),
);
},
leading: const Icon(Icons.zoom_in),
title: const Text('Zoom in Paint and Main Editor'),
trailing: const Icon(Icons.chevron_right),
);
}

Widget _buildEditor() {
return ProImageEditor.asset(
ExampleConstants.of(context)!.demoAssetPath,
key: editorKey,
callbacks: ProImageEditorCallbacks(
onImageEditingStarted: onImageEditingStarted,
onImageEditingComplete: onImageEditingComplete,
onCloseEditor: onCloseEditor,
),
configs: ProImageEditorConfigs(
designMode: platformDesignMode,
mainEditorConfigs: const MainEditorConfigs(
editorIsZoomable: true,
),
paintEditorConfigs: const PaintEditorConfigs(
editorIsZoomable: true,
),
icons: const ImageEditorIcons(
paintingEditor: IconsPaintingEditor(
moveAndZoom: Icons.pinch_outlined,
),
),
i18n: const I18n(
paintEditor: I18nPaintingEditor(
moveAndZoom: 'Zoom',
),
),
),
);
}
}
1 change: 1 addition & 0 deletions example/lib/utils/example_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ mixin ExampleHelperState<T extends StatefulWidget> on State<T> {
if (editedBytes != null) {
await precacheImage(MemoryImage(editedBytes!), context);
if (!mounted) return;
editorKey.currentState?.disablePopScope = true;
await Navigator.push(
context,
MaterialPageRoute(
Expand Down
4 changes: 4 additions & 0 deletions lib/mixins/converted_configs.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
// Project imports:
import 'package:pro_image_editor/models/editor_configs/main_editor_configs.dart';
import 'package:pro_image_editor/pro_image_editor.dart';

/// A mixin providing access to converted configurations from [ProImageEditorConfigs].
mixin ImageEditorConvertedConfigs {
/// Returns the main configuration options for the editor.
ProImageEditorConfigs get configs;

/// Returns the configuration options for the main editor.
MainEditorConfigs get mainEditorConfigs => configs.mainEditorConfigs;

/// Returns the configuration options for the paint editor.
PaintEditorConfigs get paintEditorConfigs => configs.paintEditorConfigs;

Expand Down
65 changes: 65 additions & 0 deletions lib/models/editor_callbacks/main_editor_callbacks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,68 @@ class MainEditorCallbacks extends StandaloneEditorCallbacks {
/// The [ScaleEndDetails] parameter provides information about the scaling gesture.
final ValueChanged<ScaleEndDetails>? onScaleEnd;

/// Called when the user ends a pan or scale gesture on the widget.
///
/// At the time this is called, the [TransformationController] will have
/// already been updated to reflect the change caused by the interaction,
/// though a pan may cause an inertia animation after this is called as well.
///
/// {@template flutter.widgets.InteractiveViewer.onInteractionEnd}
/// Will be called even if the interaction is disabled with [panEnabled] or
/// [scaleEnabled] for both touch gestures and mouse interactions.
///
/// A [GestureDetector] wrapping the InteractiveViewer will not respond to
/// [GestureDetector.onScaleStart], [GestureDetector.onScaleUpdate], and
/// [GestureDetector.onScaleEnd]. Use [onEditorZoomScaleStart],
/// [onEditorZoomScaleUpdate], and [onEditorZoomScaleEnd] to respond to those
/// gestures.
/// {@endtemplate}
///
/// See also:
///
/// * [onEditorZoomScaleStart], which handles the start of the same interaction.
/// * [onEditorZoomScaleUpdate], which handles an update to the same interaction.
final GestureScaleEndCallback? onEditorZoomScaleEnd;

/// Called when the user begins a pan or scale gesture on the editor.
///
/// At the time this is called, the [TransformationController] will not have
/// changed due to this interaction.
///
/// {@macro flutter.widgets.InteractiveViewer.onInteractionEnd}
///
/// The coordinates provided in the details' `focalPoint` and
/// `localFocalPoint` are normal Flutter event coordinates, not
/// InteractiveViewer scene coordinates. See
/// [TransformationController.toScene] for how to convert these coordinates to
/// scene coordinates relative to the child.
///
/// See also:
///
/// * [onEditorZoomScaleUpdate], which handles an update to the same interaction.
/// * [onEditorZoomScaleEnd], which handles the end of the same interaction.
final GestureScaleStartCallback? onEditorZoomScaleStart;

/// Called when the user updates a pan or scale gesture on the editor.
///
/// At the time this is called, the [TransformationController] will have
/// already been updated to reflect the change caused by the interaction, if
/// the interaction caused the matrix to change.
///
/// {@macro flutter.widgets.InteractiveViewer.onEditorZoomScaleEnd}
///
/// The coordinates provided in the details' `focalPoint` and
/// `localFocalPoint` are normal Flutter event coordinates, not
/// InteractiveViewer scene coordinates. See
/// [TransformationController.toScene] for how to convert these coordinates to
/// scene coordinates relative to the child.
///
/// See also:
///
/// * [onEditorZoomScaleStart], which handles the start of the same interaction.
/// * [onEditorZoomScaleEnd], which handles the end of the same interaction.
final GestureScaleUpdateCallback? onEditorZoomScaleUpdate;

/// Creates a new instance of [MainEditorCallbacks].
const MainEditorCallbacks({
this.onTap,
Expand All @@ -70,6 +132,9 @@ class MainEditorCallbacks extends StandaloneEditorCallbacks {
this.onScaleStart,
this.onScaleUpdate,
this.onScaleEnd,
this.onEditorZoomScaleStart,
this.onEditorZoomScaleUpdate,
this.onEditorZoomScaleEnd,
super.onInit,
super.onAfterViewInit,
super.onUpdateUI,
Expand Down
65 changes: 65 additions & 0 deletions lib/models/editor_callbacks/paint_editor_callbacks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,78 @@ class PaintEditorCallbacks extends StandaloneEditorCallbacks {
/// A callback function that is triggered when the color is changed.
final Function()? onColorChanged;

/// Called when the user ends a pan or scale gesture on the widget.
///
/// At the time this is called, the [TransformationController] will have
/// already been updated to reflect the change caused by the interaction,
/// though a pan may cause an inertia animation after this is called as well.
///
/// {@template flutter.widgets.InteractiveViewer.onInteractionEnd}
/// Will be called even if the interaction is disabled with [panEnabled] or
/// [scaleEnabled] for both touch gestures and mouse interactions.
///
/// A [GestureDetector] wrapping the InteractiveViewer will not respond to
/// [GestureDetector.onScaleStart], [GestureDetector.onScaleUpdate], and
/// [GestureDetector.onScaleEnd]. Use [onEditorZoomScaleStart],
/// [onEditorZoomScaleUpdate], and [onEditorZoomScaleEnd] to respond to those
/// gestures.
/// {@endtemplate}
///
/// See also:
///
/// * [onEditorZoomScaleStart], which handles the start of the same interaction.
/// * [onEditorZoomScaleUpdate], which handles an update to the same interaction.
final GestureScaleEndCallback? onEditorZoomScaleEnd;

/// Called when the user begins a pan or scale gesture on the editor.
///
/// At the time this is called, the [TransformationController] will not have
/// changed due to this interaction.
///
/// {@macro flutter.widgets.InteractiveViewer.onInteractionEnd}
///
/// The coordinates provided in the details' `focalPoint` and
/// `localFocalPoint` are normal Flutter event coordinates, not
/// InteractiveViewer scene coordinates. See
/// [TransformationController.toScene] for how to convert these coordinates to
/// scene coordinates relative to the child.
///
/// See also:
///
/// * [onEditorZoomScaleUpdate], which handles an update to the same interaction.
/// * [onEditorZoomScaleEnd], which handles the end of the same interaction.
final GestureScaleStartCallback? onEditorZoomScaleStart;

/// Called when the user updates a pan or scale gesture on the editor.
///
/// At the time this is called, the [TransformationController] will have
/// already been updated to reflect the change caused by the interaction, if
/// the interaction caused the matrix to change.
///
/// {@macro flutter.widgets.InteractiveViewer.onEditorZoomScaleEnd}
///
/// The coordinates provided in the details' `focalPoint` and
/// `localFocalPoint` are normal Flutter event coordinates, not
/// InteractiveViewer scene coordinates. See
/// [TransformationController.toScene] for how to convert these coordinates to
/// scene coordinates relative to the child.
///
/// See also:
///
/// * [onEditorZoomScaleStart], which handles the start of the same interaction.
/// * [onEditorZoomScaleEnd], which handles the end of the same interaction.
final GestureScaleUpdateCallback? onEditorZoomScaleUpdate;

/// Creates a new instance of [PaintEditorCallbacks].
const PaintEditorCallbacks({
this.onPaintModeChanged,
this.onDrawingDone,
this.onColorChanged,
this.onLineWidthChanged,
this.onToggleFill,
this.onEditorZoomScaleStart,
this.onEditorZoomScaleUpdate,
this.onEditorZoomScaleEnd,
super.onInit,
super.onAfterViewInit,
super.onUndo,
Expand Down
37 changes: 37 additions & 0 deletions lib/models/editor_configs/main_editor_configs.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/// Configuration options for a main editor.
class MainEditorConfigs {
/// Indicates whether the editor supports zoom functionality.
///
/// When set to `true`, the editor allows users to zoom in and out, providing
/// enhanced accessibility and usability, especially on smaller screens or for
/// users with visual impairments. If set to `false`, the zoom functionality
/// is disabled, and the editor's content remains at a fixed scale.
///
/// Default value is `false`.
final bool editorIsZoomable;

/// The minimum scale factor for the editor.
///
/// This value determines the lowest level of zoom that can be applied to the
/// editor content. It only has an effect when [editorIsZoomable] is set to `true`.
/// If [editorIsZoomable] is `false`, this value is ignored.
///
/// Default value is 1.0.
final double editorMinScale;

/// The maximum scale factor for the editor.
///
/// This value determines the highest level of zoom that can be applied to the
/// editor content. It only has an effect when [editorIsZoomable] is set to `true`.
/// If [editorIsZoomable] is `false`, this value is ignored.
///
/// Default value is 5.0.
final double editorMaxScale;

/// Creates an instance of MainEditorConfigs with optional settings.
const MainEditorConfigs({
this.editorIsZoomable = false,
this.editorMinScale = 1.0,
this.editorMaxScale = 5.0,
});
}
37 changes: 35 additions & 2 deletions lib/models/editor_configs/paint_editor_configs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ class PaintEditorConfigs {
/// Indicates whether the paint editor is enabled.
final bool enabled;

/// Indicates whether the editor supports zoom functionality.
///
/// When set to `true`, the editor allows users to zoom in and out, providing
/// enhanced accessibility and usability, especially on smaller screens or for
/// users with visual impairments. If set to `false`, the zoom functionality
/// is disabled, and the editor's content remains at a fixed scale.
///
/// Default value is `false`.
final bool editorIsZoomable;

/// Indicating whether the free-style drawing option is available.
final bool hasOptionFreeStyle;

Expand Down Expand Up @@ -81,6 +91,24 @@ class PaintEditorConfigs {
/// Indicates the initial paint mode.
final PaintModeE initialPaintMode;

/// The minimum scale factor for the editor.
///
/// This value determines the lowest level of zoom that can be applied to the
/// editor content. It only has an effect when [editorIsZoomable] is set to `true`.
/// If [editorIsZoomable] is `false`, this value is ignored.
///
/// Default value is 1.0.
final double editorMinScale;

/// The maximum scale factor for the editor.
///
/// This value determines the highest level of zoom that can be applied to the
/// editor content. It only has an effect when [editorIsZoomable] is set to `true`.
/// If [editorIsZoomable] is `false`, this value is ignored.
///
/// Default value is 5.0.
final double editorMaxScale;

/// The minimum scale factor from the layer.
final double minScale;

Expand All @@ -93,6 +121,9 @@ class PaintEditorConfigs {
/// Other properties are set to reasonable defaults.
const PaintEditorConfigs({
this.enabled = true,
this.editorIsZoomable = false,
this.editorMinScale = 1.0,
this.editorMaxScale = 5.0,
this.hasOptionFreeStyle = true,
this.hasOptionArrow = true,
this.hasOptionLine = true,
Expand All @@ -109,6 +140,8 @@ class PaintEditorConfigs {
this.freeStyleHighPerformanceMoving,
this.freeStyleHighPerformanceHero = false,
this.initialPaintMode = PaintModeE.freeStyle,
}) : assert(maxScale >= minScale,
'maxScale must be greater than or equal to minScale');
}) : assert(maxScale >= minScale,
'maxScale must be greater than or equal to minScale'),
assert(editorMaxScale > editorMinScale,
'editorMaxScale must be greater than editorMinScale');
}
Loading

0 comments on commit 721f072

Please sign in to comment.