Skip to content

Commit

Permalink
feat(custom-widgets): add custom widgets to replace layer interaction…
Browse files Browse the repository at this point in the history
… buttons (edit, remove, rotate, scale)
  • Loading branch information
hm21 committed Sep 19, 2024
1 parent c1ab630 commit d3d94bb
Show file tree
Hide file tree
Showing 7 changed files with 328 additions and 53 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## 5.3.0
- **Feat**(Custom-Widgets): add custom widgets to replace layer interaction buttons (edit, remove, rotateScale)

## 5.2.3
- **Fix**(Import): Ensure imported numbers are type-safe even if int and double are incorrect. This resolve issue [#221](https://github.com/hm21/pro_image_editor/issues/221)

Expand Down
116 changes: 116 additions & 0 deletions example/lib/pages/custom_appbar_bottombar_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flutter/material.dart';

// Package imports:
import 'package:google_fonts/google_fonts.dart';
import 'package:pro_image_editor/models/custom_widgets/custom_widgets_layer_interaction.dart';
import 'package:pro_image_editor/pro_image_editor.dart';

// Project imports:
Expand Down Expand Up @@ -74,6 +75,8 @@ class _CustomAppbarBottombarExampleState

final String _url = 'https://picsum.photos/id/237/2000';

final _layerInteractionButtonRadius = 10.0;

@override
void initState() {
_bottomBarScrollCtrl = ScrollController();
Expand Down Expand Up @@ -193,6 +196,119 @@ class _CustomAppbarBottombarExampleState
builder: (_) => _appBarBlurEditor(blurEditor),
),
),
layerInteraction: CustomWidgetsLayerInteraction(
editIcon:
(rebuildStream, onTap, toggleTooltipVisibility, rotation) =>
ReactiveCustomWidget(
builder: (_) {
return Positioned(
top: 0,
right: 0,
child: Transform.rotate(
angle: rotation,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: onTap,
child: Tooltip(
message: 'Edit',
child: Container(
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
_layerInteractionButtonRadius * 2),
color: Colors.white,
),
child: Icon(
Icons.edit,
color: Colors.black,
size: _layerInteractionButtonRadius * 2,
),
),
),
),
),
),
);
},
stream: rebuildStream,
),
removeIcon:
(rebuildStream, onTap, toggleTooltipVisibility, rotation) =>
ReactiveCustomWidget(
builder: (_) {
return Positioned(
top: 0,
left: 0,
child: Transform.rotate(
angle: rotation,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: onTap,
child: Tooltip(
message: 'Remove',
child: Container(
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
_layerInteractionButtonRadius * 2),
color: Colors.white,
),
child: Icon(
Icons.close,
color: Colors.black,
size: _layerInteractionButtonRadius * 2,
),
),
),
),
),
),
);
},
stream: rebuildStream,
),
rotateScaleIcon: (rebuildStream, onScaleRotateDown,
onScaleRotateUp, toggleTooltipVisibility, rotation) =>
ReactiveCustomWidget(
builder: (_) {
return Positioned(
/// IMPORTANT: The editor currently only supports this
/// position for rotation to function correctly
bottom: 0,
right: 0,
child: Transform.rotate(
angle: rotation,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: Listener(
onPointerDown: onScaleRotateDown,
onPointerUp: onScaleRotateUp,
child: Tooltip(
message: 'Rotate',
child: Container(
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
_layerInteractionButtonRadius * 2),
color: Colors.white,
),
child: Icon(
Icons.rotate_90_degrees_ccw,
color: Colors.black,
size: _layerInteractionButtonRadius * 2,
),
),
),
),
),
),
);
},
stream: rebuildStream,
),
),
),
textEditorConfigs: TextEditorConfigs(
showSelectFontStyleBottomBar: true,
Expand Down
9 changes: 9 additions & 0 deletions lib/models/custom_widgets/custom_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import 'package:flutter/widgets.dart';
import 'package:pro_image_editor/models/editor_configs/pro_image_editor_configs.dart';

import 'custom_widgets_layer_interaction.dart';

export 'custom_widgets_blur_editor.dart';
export 'custom_widgets_crop_rotate_editor.dart';
export 'custom_widgets_filter_editor.dart';
Expand All @@ -25,6 +27,7 @@ class ImageEditorCustomWidgets {
this.cropRotateEditor = const CustomWidgetsCropRotateEditor(),
this.filterEditor = const CustomWidgetsFilterEditor(),
this.blurEditor = const CustomWidgetsBlurEditor(),
this.layerInteraction = const CustomWidgetsLayerInteraction(),
});

/// The main editor instance.
Expand All @@ -45,6 +48,10 @@ class ImageEditorCustomWidgets {
/// The blur editor instance.
final CustomWidgetsBlurEditor blurEditor;

/// Defines the set of interactions (edit, remove, rotate/scale) for
/// custom widgets.
final CustomWidgetsLayerInteraction layerInteraction;

/// Replace the existing loading dialog.
///
/// **Example:**
Expand Down Expand Up @@ -128,6 +135,7 @@ class ImageEditorCustomWidgets {
Widget Function(String message, ProImageEditorConfigs configs)?
loadingDialog,
Widget? circularProgressIndicator,
CustomWidgetsLayerInteraction? layerInteraction,
}) {
return ImageEditorCustomWidgets(
mainEditor: mainEditor ?? this.mainEditor,
Expand All @@ -139,6 +147,7 @@ class ImageEditorCustomWidgets {
loadingDialog: loadingDialog ?? this.loadingDialog,
circularProgressIndicator:
circularProgressIndicator ?? this.circularProgressIndicator,
layerInteraction: layerInteraction ?? this.layerInteraction,
);
}
}
61 changes: 61 additions & 0 deletions lib/models/custom_widgets/custom_widgets_layer_interaction.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'utils/custom_widgets_typedef.dart';

/// A class that defines a layer of interaction for custom widgets,
/// allowing editing, removing, and rotating/scaling actions.
///
/// Each interaction can be represented by specific buttons passed into
/// the constructor.
///
/// Example usage:
/// ```dart
/// CustomWidgetsLayerInteraction(
/// editIcon: LayerInteractionTapButton(...),
/// removeIcon: LayerInteractionTapButton(...),
/// rotateScaleIcon: LayerInteractionScaleRotateButton(...),
/// );
/// ```
class CustomWidgetsLayerInteraction {
/// Creates a [CustomWidgetsLayerInteraction] with optional interaction
/// buttons.
///
/// * [editIcon]: A button that triggers the edit action.
/// * [removeIcon]: A button that triggers the remove action.
/// * [rotateScaleIcon]: A button for rotate/scale actions.
const CustomWidgetsLayerInteraction({
this.editIcon,
this.removeIcon,
this.rotateScaleIcon,
});

/// The button for the edit interaction, represented by
/// [LayerInteractionTapButton].
final LayerInteractionTapButton? editIcon;

/// The button for the remove interaction, represented by
/// [LayerInteractionTapButton].
final LayerInteractionTapButton? removeIcon;

/// The button for the rotate/scale interaction, represented by
/// [LayerInteractionScaleRotateButton].
final LayerInteractionScaleRotateButton? rotateScaleIcon;

/// Returns a copy of this object with the given fields updated.
///
/// If no values are provided for the fields, the current values will be kept.
///
/// * [editIcon]: Updates the button for the edit action.
/// * [removeIcon]: Updates the button for the remove action.
/// * [rotateScaleIcon]: Updates the button for rotate/scale actions.
CustomWidgetsLayerInteraction copyWith({
LayerInteractionTapButton? editIcon,
LayerInteractionTapButton? removeIcon,
LayerInteractionScaleRotateButton? rotateScaleIcon,
}) {
return CustomWidgetsLayerInteraction(
editIcon: editIcon ?? this.editIcon,
removeIcon: removeIcon ?? this.removeIcon,
rotateScaleIcon: rotateScaleIcon ?? this.rotateScaleIcon,
);
}
}
44 changes: 44 additions & 0 deletions lib/models/custom_widgets/utils/custom_widgets_typedef.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,47 @@ typedef CustomSlider<T> = ReactiveCustomWidget Function(
Function(double value) onChanged,
Function(double value) onChangeEnd,
);

/// A typedef for a function that creates a [ReactiveCustomWidget] for a tap
/// interaction.
///
/// The function takes the following parameters:
///
/// * [rebuildStream]: A stream that triggers the widget to rebuild.
/// * [onTap]: A callback function that is invoked when the widget is tapped.
/// * [toggleTooltipVisibility]: A function that toggles the visibility of a
/// tooltip based on the boolean value passed.
/// * [rotation]: A double value representing the current rotation of the
/// widget.
///
/// Returns a nullable [ReactiveCustomWidget].
typedef LayerInteractionTapButton = ReactiveCustomWidget? Function(
Stream<void> rebuildStream,
Function() onTap,
Function(bool) toggleTooltipVisibility,
double rotation,
);

/// A typedef for a function that creates a [ReactiveCustomWidget] for scale
/// and rotate interactions.
///
/// The function takes the following parameters:
///
/// * [rebuildStream]: A stream that triggers the widget to rebuild.
/// * [onScaleRotateDown]: A callback function that is invoked when the
/// scale/rotate action starts (on pointer down event).
/// * [onScaleRotateUp]: A callback function that is invoked when the
/// scale/rotate action ends (on pointer up event).
/// * [toggleTooltipVisibility]: A function that toggles the visibility of a
/// tooltip based on the boolean value passed.
/// * [rotation]: A double value representing the current rotation of the
/// widget.
///
/// Returns a nullable [ReactiveCustomWidget].
typedef LayerInteractionScaleRotateButton = ReactiveCustomWidget? Function(
Stream<void> rebuildStream,
Function(PointerDownEvent) onScaleRotateDown,
Function(PointerUpEvent) onScaleRotateUp,
Function(bool) toggleTooltipVisibility,
double rotation,
);
Loading

0 comments on commit d3d94bb

Please sign in to comment.