From 048717a0fb3b041ef9fa344fec8ca3dcd6d90295 Mon Sep 17 00:00:00 2001 From: Hasan Keskin Date: Mon, 12 May 2025 16:21:49 +0200 Subject: [PATCH 1/8] Create cursor_tool object and provider. --- ios/Podfile.lock | 42 ++++++------- ios/Runner.xcodeproj/project.pbxproj | 18 ++++++ .../xcshareddata/xcschemes/Runner.xcscheme | 1 + .../object/tools/cursor_tool_provider.dart | 23 +++++++ .../object/tools/cursor_tool_provider.g.dart | 27 ++++++++ .../state/toolbox_state_provider.dart | 5 ++ .../tools/implementation/cursor_tool.dart | 61 +++++++++++++++++++ 7 files changed, 156 insertions(+), 21 deletions(-) create mode 100644 lib/core/providers/object/tools/cursor_tool_provider.dart create mode 100644 lib/core/providers/object/tools/cursor_tool_provider.g.dart create mode 100644 lib/core/tools/implementation/cursor_tool.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b22394e1..a2aa913b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -42,14 +42,14 @@ PODS: - Flutter - integration_test (0.0.1): - Flutter - - launch_review (0.0.1): + - launch_review_latest (0.0.1): - Flutter - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - permission_handler_apple (9.1.1): + - permission_handler_apple (9.3.0): - Flutter - SDWebImage (5.19.2): - SDWebImage/Core (= 5.19.2) @@ -57,7 +57,7 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - - sqflite (0.0.3): + - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - SwiftyGif (5.4.5) @@ -71,12 +71,12 @@ DEPENDENCIES: - flutter_localization (from `.symlinks/plugins/flutter_localization/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - - launch_review (from `.symlinks/plugins/launch_review/ios`) + - launch_review_latest (from `.symlinks/plugins/launch_review_latest/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - - sqflite (from `.symlinks/plugins/sqflite/darwin`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) SPEC REPOS: @@ -99,8 +99,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/image_picker_ios/ios" integration_test: :path: ".symlinks/plugins/integration_test/ios" - launch_review: - :path: ".symlinks/plugins/launch_review/ios" + launch_review_latest: + :path: ".symlinks/plugins/launch_review_latest/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: @@ -109,29 +109,29 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/permission_handler_apple/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" - sqflite: - :path: ".symlinks/plugins/sqflite/darwin" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 + device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 - file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 + file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_localization: f43b18844a2b3d2c71fd64f04ffd6b1e64dd54d4 - image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425 - integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 - launch_review: 75d5a956ba8eaa493e9c9d4bf4c05e505e8d5ed0 - package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 - path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c - permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 + flutter_localization: 72299fb6cb4e51cae587bd953ed0b958040b71e6 + image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e + launch_review_latest: 28e236fc255d91ec1430c39f951c4db1398c79c1 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d SDWebImage: dfe95b2466a9823cf9f0c6d01217c06550d7b29a - shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d PODFILE CHECKSUM: aa9c826e174e713c4dad1b0d2110be4d87591fc5 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 6b863eca..be36c6c6 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -140,6 +140,7 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, C143E155B1BF9D937E877BC4 /* [CP] Embed Pods Frameworks */, + CE347BAC542BC77A1AF7E630 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -268,6 +269,23 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + CE347BAC542BC77A1AF7E630 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 5e31d3d3..c53e2b31 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -48,6 +48,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/lib/core/providers/object/tools/cursor_tool_provider.dart b/lib/core/providers/object/tools/cursor_tool_provider.dart new file mode 100644 index 00000000..389f0307 --- /dev/null +++ b/lib/core/providers/object/tools/cursor_tool_provider.dart @@ -0,0 +1,23 @@ + +import 'package:paintroid/core/tools/implementation/cursor_tool.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'package:paintroid/core/commands/command_factory/command_factory_provider.dart'; +import 'package:paintroid/core/commands/command_manager/command_manager_provider.dart'; +import 'package:paintroid/core/commands/graphic_factory/graphic_factory_provider.dart'; +import 'package:paintroid/core/enums/tool_types.dart'; + +part 'cursor_tool_provider.g.dart'; + +@riverpod +class CursorToolProvider extends _$CursorToolProvider { + @override + CursorTool build() { + return CursorTool( + commandManager: ref.watch(commandManagerProvider), + commandFactory: ref.watch(commandFactoryProvider), + graphicFactory: ref.watch(graphicFactoryProvider), + type: ToolType.CURSOR, + ); + } +} diff --git a/lib/core/providers/object/tools/cursor_tool_provider.g.dart b/lib/core/providers/object/tools/cursor_tool_provider.g.dart new file mode 100644 index 00000000..2a1d3df2 --- /dev/null +++ b/lib/core/providers/object/tools/cursor_tool_provider.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'cursor_tool_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$cursorToolProviderHash() => + r'c72647c93c396424e71f321ffa01760e8da6a522'; + +/// See also [CursorToolProvider]. +@ProviderFor(CursorToolProvider) +final cursorToolProvider = + AutoDisposeNotifierProvider.internal( + CursorToolProvider.new, + name: r'cursorToolProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$cursorToolProviderHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$CursorToolProvider = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/core/providers/state/toolbox_state_provider.dart b/lib/core/providers/state/toolbox_state_provider.dart index f46fd575..913e40a2 100644 --- a/lib/core/providers/state/toolbox_state_provider.dart +++ b/lib/core/providers/state/toolbox_state_provider.dart @@ -4,6 +4,7 @@ import 'package:paintroid/core/commands/command_manager/command_manager_provider import 'package:paintroid/core/enums/tool_types.dart'; import 'package:paintroid/core/providers/object/canvas_painter_provider.dart'; import 'package:paintroid/core/providers/object/tools/brush_tool_provider.dart'; +import 'package:paintroid/core/providers/object/tools/cursor_tool_provider.dart'; import 'package:paintroid/core/providers/object/tools/eraser_tool_provider.dart'; import 'package:paintroid/core/providers/object/tools/hand_tool_provider.dart'; import 'package:paintroid/core/providers/object/tools/line_tool_provider.dart'; @@ -77,6 +78,10 @@ class ToolBoxStateProvider extends _$ToolBoxStateProvider { (state.currentTool as SprayTool).updateSprayRadius(currentStrokeWidth); ref.read(paintProvider.notifier).updateStrokeWidth(SPRAY_TOOL_RADIUS); break; + case ToolType.CURSOR: + state = state.copyWith(currentTool: ref.read(cursorToolProvider)); + print("Changed tool to cursor"); + break; default: state = state.copyWith(currentTool: ref.read(brushToolProvider)); break; diff --git a/lib/core/tools/implementation/cursor_tool.dart b/lib/core/tools/implementation/cursor_tool.dart new file mode 100644 index 00000000..34aeafd4 --- /dev/null +++ b/lib/core/tools/implementation/cursor_tool.dart @@ -0,0 +1,61 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; + +import 'package:paintroid/core/commands/graphic_factory/graphic_factory.dart'; +import 'package:paintroid/core/commands/path_with_action_history.dart'; +import 'package:paintroid/core/tools/tool.dart'; + +class CursorTool extends Tool { + final GraphicFactory graphicFactory; + + @visibleForTesting + late PathWithActionHistory pathToDraw; + + CursorTool({ + required super.commandFactory, + required super.commandManager, + required this.graphicFactory, + required super.type, + super.hasAddFunctionality = false, + super.hasFinalizeFunctionality = false, + }); + + @override + void onDown(Offset point, Paint paint) { + print("On Down was pressed"); + } + + @override + void onDrag(Offset point, Paint paint) { + print("On Drag was pressed"); + + + } + + @override + void onUp(Offset point, Paint paint) { + print("On Up was pressed"); + + } + + @override + void onCancel() { + } + + @override + void onCheckmark(Paint paint) {} + + @override + void onPlus() {} + + @override + void onRedo() { + commandManager.redo(); + } + + @override + void onUndo() { + commandManager.undo(); + } +} From 2a15bf8bf7c0df4986eec60f9dcf3cffe91900e0 Mon Sep 17 00:00:00 2001 From: Hasan Keskin Date: Wed, 14 May 2025 17:53:25 +0200 Subject: [PATCH 2/8] delete empty lines --- lib/core/tools/implementation/cursor_tool.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/core/tools/implementation/cursor_tool.dart b/lib/core/tools/implementation/cursor_tool.dart index 34aeafd4..0e5fcb29 100644 --- a/lib/core/tools/implementation/cursor_tool.dart +++ b/lib/core/tools/implementation/cursor_tool.dart @@ -29,14 +29,11 @@ class CursorTool extends Tool { @override void onDrag(Offset point, Paint paint) { print("On Drag was pressed"); - - } @override void onUp(Offset point, Paint paint) { print("On Up was pressed"); - } @override From 05094727b2129372780db6288fb3fc8b879c8b4b Mon Sep 17 00:00:00 2001 From: Hasan Keskin Date: Wed, 25 Jun 2025 13:55:37 +0200 Subject: [PATCH 3/8] Draw cursor icon as SVG in Stack --- assets/icon/cursor_icon.svg | 52 +++++++++++++++++++ lib/core/commands/command_painter.dart | 6 +++ .../object/tools/cursor_tool_provider.dart | 2 +- .../providers/state/canvas_state_data.dart | 1 + .../state/canvas_state_data.freezed.dart | 35 ++++++++++--- .../state/canvas_state_provider.dart | 28 ++++++---- .../state/canvas_state_provider.g.dart | 2 +- .../state/toolbox_state_provider.dart | 8 ++- .../state/toolbox_state_provider.g.dart | 2 +- lib/core/tools/cursor_tool/cursor_icon.dart | 28 ++++++++++ .../cursor_tool.dart | 8 ++- lib/core/tools/tool.dart | 8 ++- .../drawing_surface/canvas_painter.dart | 45 +++++++++++----- .../render_image_for_export_test.dart | 3 ++ 14 files changed, 190 insertions(+), 38 deletions(-) create mode 100644 assets/icon/cursor_icon.svg create mode 100644 lib/core/tools/cursor_tool/cursor_icon.dart rename lib/core/tools/{implementation => cursor_tool}/cursor_tool.dart (78%) diff --git a/assets/icon/cursor_icon.svg b/assets/icon/cursor_icon.svg new file mode 100644 index 00000000..614e7fb8 --- /dev/null +++ b/assets/icon/cursor_icon.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + diff --git a/lib/core/commands/command_painter.dart b/lib/core/commands/command_painter.dart index 02ffc50c..759a56af 100644 --- a/lib/core/commands/command_painter.dart +++ b/lib/core/commands/command_painter.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:paintroid/core/commands/command_manager/command_manager.dart'; import 'package:paintroid/core/commands/command_manager/command_manager_provider.dart'; import 'package:paintroid/core/enums/tool_types.dart'; +import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; import 'package:paintroid/core/providers/state/paint_provider.dart'; import 'package:paintroid/core/providers/state/toolbox_state_provider.dart'; import 'package:paintroid/core/tools/implementation/shapes_tool/shapes_tool.dart'; @@ -23,6 +24,7 @@ class CommandPainter extends CustomPainter { if (currentTool.type != ToolType.SHAPES) { canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); } + print('PAIJT'); switch (currentTool.type) { case ToolType.LINE: _drawGhostPathsAndVertices(canvas, currentTool as LineTool); @@ -32,6 +34,10 @@ class CommandPainter extends CustomPainter { ..drawShape(canvas, ref.read(paintProvider)) ..drawGuides(canvas); break; + case ToolType.CURSOR: + print('in case'); + ref.read(canvasStateProvider.notifier).updateCursorPosition(currentTool.iconPosition!); + print(currentTool.iconPosition); default: commandManager.executeLastCommand(canvas); break; diff --git a/lib/core/providers/object/tools/cursor_tool_provider.dart b/lib/core/providers/object/tools/cursor_tool_provider.dart index 389f0307..c78854ec 100644 --- a/lib/core/providers/object/tools/cursor_tool_provider.dart +++ b/lib/core/providers/object/tools/cursor_tool_provider.dart @@ -1,5 +1,5 @@ -import 'package:paintroid/core/tools/implementation/cursor_tool.dart'; +import 'package:paintroid/core/tools/cursor_tool/cursor_tool.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:paintroid/core/commands/command_factory/command_factory_provider.dart'; diff --git a/lib/core/providers/state/canvas_state_data.dart b/lib/core/providers/state/canvas_state_data.dart index f5a0cbe2..17f928f1 100644 --- a/lib/core/providers/state/canvas_state_data.dart +++ b/lib/core/providers/state/canvas_state_data.dart @@ -16,5 +16,6 @@ class CanvasStateData with _$CanvasStateData { required Size size, required CommandManager commandManager, required GraphicFactory graphicFactory, + required Offset cursorPosition, }) = _CanvasStateData; } diff --git a/lib/core/providers/state/canvas_state_data.freezed.dart b/lib/core/providers/state/canvas_state_data.freezed.dart index e90275db..2d78eade 100644 --- a/lib/core/providers/state/canvas_state_data.freezed.dart +++ b/lib/core/providers/state/canvas_state_data.freezed.dart @@ -21,6 +21,7 @@ mixin _$CanvasStateData { ui.Size get size => throw _privateConstructorUsedError; CommandManager get commandManager => throw _privateConstructorUsedError; GraphicFactory get graphicFactory => throw _privateConstructorUsedError; + ui.Offset get cursorPosition => throw _privateConstructorUsedError; /// Create a copy of CanvasStateData /// with the given fields replaced by the non-null parameter values. @@ -40,7 +41,8 @@ abstract class $CanvasStateDataCopyWith<$Res> { ui.Image? cachedImage, ui.Size size, CommandManager commandManager, - GraphicFactory graphicFactory}); + GraphicFactory graphicFactory, + ui.Offset cursorPosition}); } /// @nodoc @@ -63,6 +65,7 @@ class _$CanvasStateDataCopyWithImpl<$Res, $Val extends CanvasStateData> Object? size = null, Object? commandManager = null, Object? graphicFactory = null, + Object? cursorPosition = null, }) { return _then(_value.copyWith( backgroundImage: freezed == backgroundImage @@ -85,6 +88,10 @@ class _$CanvasStateDataCopyWithImpl<$Res, $Val extends CanvasStateData> ? _value.graphicFactory : graphicFactory // ignore: cast_nullable_to_non_nullable as GraphicFactory, + cursorPosition: null == cursorPosition + ? _value.cursorPosition + : cursorPosition // ignore: cast_nullable_to_non_nullable + as ui.Offset, ) as $Val); } } @@ -102,7 +109,8 @@ abstract class _$$CanvasStateDataImplCopyWith<$Res> ui.Image? cachedImage, ui.Size size, CommandManager commandManager, - GraphicFactory graphicFactory}); + GraphicFactory graphicFactory, + ui.Offset cursorPosition}); } /// @nodoc @@ -123,6 +131,7 @@ class __$$CanvasStateDataImplCopyWithImpl<$Res> Object? size = null, Object? commandManager = null, Object? graphicFactory = null, + Object? cursorPosition = null, }) { return _then(_$CanvasStateDataImpl( backgroundImage: freezed == backgroundImage @@ -145,6 +154,10 @@ class __$$CanvasStateDataImplCopyWithImpl<$Res> ? _value.graphicFactory : graphicFactory // ignore: cast_nullable_to_non_nullable as GraphicFactory, + cursorPosition: null == cursorPosition + ? _value.cursorPosition + : cursorPosition // ignore: cast_nullable_to_non_nullable + as ui.Offset, )); } } @@ -157,7 +170,8 @@ class _$CanvasStateDataImpl implements _CanvasStateData { this.cachedImage, required this.size, required this.commandManager, - required this.graphicFactory}); + required this.graphicFactory, + required this.cursorPosition}); @override final ui.Image? backgroundImage; @@ -169,10 +183,12 @@ class _$CanvasStateDataImpl implements _CanvasStateData { final CommandManager commandManager; @override final GraphicFactory graphicFactory; + @override + final ui.Offset cursorPosition; @override String toString() { - return 'CanvasStateData(backgroundImage: $backgroundImage, cachedImage: $cachedImage, size: $size, commandManager: $commandManager, graphicFactory: $graphicFactory)'; + return 'CanvasStateData(backgroundImage: $backgroundImage, cachedImage: $cachedImage, size: $size, commandManager: $commandManager, graphicFactory: $graphicFactory, cursorPosition: $cursorPosition)'; } @override @@ -188,12 +204,14 @@ class _$CanvasStateDataImpl implements _CanvasStateData { (identical(other.commandManager, commandManager) || other.commandManager == commandManager) && (identical(other.graphicFactory, graphicFactory) || - other.graphicFactory == graphicFactory)); + other.graphicFactory == graphicFactory) && + (identical(other.cursorPosition, cursorPosition) || + other.cursorPosition == cursorPosition)); } @override int get hashCode => Object.hash(runtimeType, backgroundImage, cachedImage, - size, commandManager, graphicFactory); + size, commandManager, graphicFactory, cursorPosition); /// Create a copy of CanvasStateData /// with the given fields replaced by the non-null parameter values. @@ -211,7 +229,8 @@ abstract class _CanvasStateData implements CanvasStateData { final ui.Image? cachedImage, required final ui.Size size, required final CommandManager commandManager, - required final GraphicFactory graphicFactory}) = _$CanvasStateDataImpl; + required final GraphicFactory graphicFactory, + required final ui.Offset cursorPosition}) = _$CanvasStateDataImpl; @override ui.Image? get backgroundImage; @@ -223,6 +242,8 @@ abstract class _CanvasStateData implements CanvasStateData { CommandManager get commandManager; @override GraphicFactory get graphicFactory; + @override + ui.Offset get cursorPosition; /// Create a copy of CanvasStateData /// with the given fields replaced by the non-null parameter values. diff --git a/lib/core/providers/state/canvas_state_provider.dart b/lib/core/providers/state/canvas_state_provider.dart index 272cbe1f..9cdc88b5 100644 --- a/lib/core/providers/state/canvas_state_provider.dart +++ b/lib/core/providers/state/canvas_state_provider.dart @@ -18,27 +18,33 @@ class CanvasStateProvider extends _$CanvasStateProvider { @override CanvasStateData build() { initialCanvasSize = ref.watch(IDeviceService.sizeProvider).when( - data: (size) => size, - error: (_, __) => widgets.WidgetsBinding.instance.platformDispatcher - .views.first.physicalSize, - loading: () => Size.zero, - ); + data: (size) => size, + error: (_, __) => widgets.WidgetsBinding.instance.platformDispatcher + .views.first.physicalSize, + loading: () => Size.zero, + ); return CanvasStateData( size: initialCanvasSize, commandManager: ref.watch(commandManagerProvider), graphicFactory: ref.watch(graphicFactoryProvider), + cursorPosition: Offset(initialCanvasSize.width / 2, initialCanvasSize.height / 2), // Initialize at center ); } + void updateCursorPosition(Offset position) { + print('updated'); + state = state.copyWith(cursorPosition: position); + } + void setBackgroundImage(Image image) => state = state.copyWith( - backgroundImage: image, - size: Size(image.width.toDouble(), image.height.toDouble()), - ); + backgroundImage: image, + size: Size(image.width.toDouble(), image.height.toDouble()), + ); void clearBackgroundImageAndResetDimensions() => state = state.copyWith( - backgroundImage: null, - size: initialCanvasSize, - ); + backgroundImage: null, + size: initialCanvasSize, + ); Future updateCachedImage() async { final recorder = state.graphicFactory.createPictureRecorder(); diff --git a/lib/core/providers/state/canvas_state_provider.g.dart b/lib/core/providers/state/canvas_state_provider.g.dart index 377d5217..d013af1f 100644 --- a/lib/core/providers/state/canvas_state_provider.g.dart +++ b/lib/core/providers/state/canvas_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'canvas_state_provider.dart'; // ************************************************************************** String _$canvasStateProviderHash() => - r'679bba9b579d049bcfbf4cc5231ae14a2d5baeab'; + r'dc37460b6736868558f365325aa70bbaaa738408'; /// See also [CanvasStateProvider]. @ProviderFor(CanvasStateProvider) diff --git a/lib/core/providers/state/toolbox_state_provider.dart b/lib/core/providers/state/toolbox_state_provider.dart index 913e40a2..a19cad2b 100644 --- a/lib/core/providers/state/toolbox_state_provider.dart +++ b/lib/core/providers/state/toolbox_state_provider.dart @@ -80,7 +80,6 @@ class ToolBoxStateProvider extends _$ToolBoxStateProvider { break; case ToolType.CURSOR: state = state.copyWith(currentTool: ref.read(cursorToolProvider)); - print("Changed tool to cursor"); break; default: state = state.copyWith(currentTool: ref.read(brushToolProvider)); @@ -89,4 +88,11 @@ class ToolBoxStateProvider extends _$ToolBoxStateProvider { ref.read(paintProvider.notifier).updateBlendModeByToolType(data.type); ToastUtils.showShortToast(message: data.name); } + + void updateIconPosition(Offset position) { + // Create a new tool with the updated position + final updatedTool = state.currentTool; + // Update the state with the new tool + state = state.copyWith(currentTool: updatedTool); + } } diff --git a/lib/core/providers/state/toolbox_state_provider.g.dart b/lib/core/providers/state/toolbox_state_provider.g.dart index 96cc83ce..d0e28b9a 100644 --- a/lib/core/providers/state/toolbox_state_provider.g.dart +++ b/lib/core/providers/state/toolbox_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'toolbox_state_provider.dart'; // ************************************************************************** String _$toolBoxStateProviderHash() => - r'23e3ddde3194c0acc46abe79fe6d0b0b4bf77ec6'; + r'27d5cd418224f6d4f51b0662e5a02c5dd6544f01'; /// See also [ToolBoxStateProvider]. @ProviderFor(ToolBoxStateProvider) diff --git a/lib/core/tools/cursor_tool/cursor_icon.dart b/lib/core/tools/cursor_tool/cursor_icon.dart new file mode 100644 index 00000000..4ddd9f30 --- /dev/null +++ b/lib/core/tools/cursor_tool/cursor_icon.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:paintroid/ui/shared/icon_svg.dart'; + +class CursorIcon extends StatelessWidget { + final Color drawColor; + final double size; + + const CursorIcon({ + required this.drawColor, + this.size = 320, + }) ; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => print('tapped'), + child: SizedBox( + width: size, + height: size, + child: IconSvg( + path: 'assets/icon/cursor_icon.svg', + height: size, + width: size, + ), + ), + ); + } +} diff --git a/lib/core/tools/implementation/cursor_tool.dart b/lib/core/tools/cursor_tool/cursor_tool.dart similarity index 78% rename from lib/core/tools/implementation/cursor_tool.dart rename to lib/core/tools/cursor_tool/cursor_tool.dart index 0e5fcb29..85b59d1f 100644 --- a/lib/core/tools/implementation/cursor_tool.dart +++ b/lib/core/tools/cursor_tool/cursor_tool.dart @@ -1,9 +1,11 @@ import 'dart:ui'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:paintroid/core/commands/graphic_factory/graphic_factory.dart'; import 'package:paintroid/core/commands/path_with_action_history.dart'; +import 'package:paintroid/core/tools/cursor_tool/cursor_icon.dart'; import 'package:paintroid/core/tools/tool.dart'; class CursorTool extends Tool { @@ -19,21 +21,25 @@ class CursorTool extends Tool { required super.type, super.hasAddFunctionality = false, super.hasFinalizeFunctionality = false, + super.icon = const CursorIcon(drawColor: Color(234234)), + super.iconPosition = const Offset(0,0), }); @override void onDown(Offset point, Paint paint) { print("On Down was pressed"); + super.iconPosition = point; } @override void onDrag(Offset point, Paint paint) { print("On Drag was pressed"); + super.iconPosition = point; } @override void onUp(Offset point, Paint paint) { - print("On Up was pressed"); + super.iconPosition = point; } @override diff --git a/lib/core/tools/tool.dart b/lib/core/tools/tool.dart index 31cd3280..c2f09cbf 100644 --- a/lib/core/tools/tool.dart +++ b/lib/core/tools/tool.dart @@ -1,8 +1,10 @@ import 'dart:ui'; +import 'package:flutter/cupertino.dart'; import 'package:paintroid/core/commands/command_factory/command_factory.dart'; import 'package:paintroid/core/commands/command_manager/command_manager.dart'; import 'package:paintroid/core/enums/tool_types.dart'; +import 'package:paintroid/core/tools/cursor_tool/cursor_icon.dart'; abstract class Tool { final ToolType type; @@ -10,13 +12,17 @@ abstract class Tool { final CommandFactory commandFactory; final bool hasAddFunctionality; final bool hasFinalizeFunctionality; + CursorIcon? icon; + Offset? iconPosition; - const Tool({ + Tool({ required this.commandManager, required this.commandFactory, required this.type, required this.hasAddFunctionality, required this.hasFinalizeFunctionality, + this.icon, + this.iconPosition, }); void onDown(Offset point, Paint paint); diff --git a/lib/ui/pages/workspace_page/components/drawing_surface/canvas_painter.dart b/lib/ui/pages/workspace_page/components/drawing_surface/canvas_painter.dart index af7206b1..ef5ab047 100644 --- a/lib/ui/pages/workspace_page/components/drawing_surface/canvas_painter.dart +++ b/lib/ui/pages/workspace_page/components/drawing_surface/canvas_painter.dart @@ -5,6 +5,7 @@ import 'package:paintroid/core/commands/command_painter.dart'; import 'package:paintroid/core/providers/object/canvas_painter_provider.dart'; import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; import 'package:paintroid/core/providers/state/paint_provider.dart'; +import 'package:paintroid/core/providers/state/toolbox_state_provider.dart'; import 'package:paintroid/core/utils/widget_identifier.dart'; import 'package:paintroid/ui/pages/workspace_page/components/drawing_surface/checkerboard_pattern.dart'; @@ -14,20 +15,36 @@ class CanvasPainter extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final size = ref.watch(canvasStateProvider.select((state) => state.size)); - return Container( - key: const ValueKey(WidgetIdentifier.canvasPainter), - width: size.width, - height: size.height, - foregroundDecoration: const BoxDecoration( - border: Border.fromBorderSide(BorderSide(width: 0.5)), - ), - child: const Stack( - fit: StackFit.expand, - children: [ - BackgroundLayer(), - PaintingLayer(), - ], - ), + final currentTool = ref.read(toolBoxStateProvider).currentTool; + final iconPosition = ref.watch( + canvasStateProvider.select((state) => state.cursorPosition) + ); + return Stack( + children: [ + Container( + key: const ValueKey(WidgetIdentifier.canvasPainter), + width: size.width, + height: size.height, + foregroundDecoration: const BoxDecoration( + border: Border.fromBorderSide(BorderSide(width: 0.5)), + ), + child: Stack( + fit: StackFit.expand, + children: [ + BackgroundLayer(), + PaintingLayer(), + if (currentTool.icon != null) + Positioned( + left: iconPosition!.dx - 160, + top: iconPosition.dy - 160, + child: currentTool.icon!, + ), + ], + ), + ), + + + ], ); } } diff --git a/test/unit/workspace/render_image_for_export_test.dart b/test/unit/workspace/render_image_for_export_test.dart index b3182919..d3aa1032 100644 --- a/test/unit/workspace/render_image_for_export_test.dart +++ b/test/unit/workspace/render_image_for_export_test.dart @@ -37,6 +37,7 @@ class MockCanvasState1 extends CanvasStateProvider { commandManager: MockCommandManager(), graphicFactory: FakeGraphicFactory(MockCanvas(), MockCanvas(), MockCanvas(), Paint()), + cursorPosition: Offset(0, 0), ); } } @@ -49,7 +50,9 @@ class MockCanvasState2 extends CanvasStateProvider { commandManager: MockCommandManager(), graphicFactory: FakeGraphicFactory(MockCanvas(), MockCanvas(), MockCanvas(), Paint()), + cursorPosition: Offset(0, 0), ); + } } From 5b0f921b4bb32a56de78d2dfd16fd16d6d3b180d Mon Sep 17 00:00:00 2001 From: Hasan Keskin Date: Sun, 6 Jul 2025 16:36:42 +0200 Subject: [PATCH 4/8] PAINTROID-790 remove SVG implementation and implement custom painter for cursor and add unit/integration tests --- assets/icon/cursor_icon.svg | 52 --- ios/Podfile.lock | 24 +- lib/core/commands/command_painter.dart | 11 +- .../object/tools/cursor_tool_provider.dart | 6 +- .../object/tools/cursor_tool_provider.g.dart | 2 +- .../providers/state/canvas_state_data.dart | 1 - .../state/canvas_state_data.freezed.dart | 35 +- .../state/canvas_state_provider.dart | 28 +- .../state/canvas_state_provider.g.dart | 2 +- .../state/toolbox_state_provider.dart | 7 - .../state/toolbox_state_provider.g.dart | 2 +- lib/core/tools/cursor_tool/cursor_icon.dart | 28 -- lib/core/tools/cursor_tool/cursor_tool.dart | 64 ---- .../tools/implementation/cursor_tool.dart | 161 +++++++++ lib/core/tools/tool.dart | 7 +- .../drawing_surface/canvas_painter.dart | 45 +-- test/integration/cursor_tool_test.dart | 335 ++++++++++++++++++ test/unit/tools/cursor_tool_test.dart | 215 +++++++++++ .../render_image_for_export_test.dart | 3 - 19 files changed, 770 insertions(+), 258 deletions(-) delete mode 100644 assets/icon/cursor_icon.svg delete mode 100644 lib/core/tools/cursor_tool/cursor_icon.dart delete mode 100644 lib/core/tools/cursor_tool/cursor_tool.dart create mode 100644 lib/core/tools/implementation/cursor_tool.dart create mode 100644 test/integration/cursor_tool_test.dart create mode 100644 test/unit/tools/cursor_tool_test.dart diff --git a/assets/icon/cursor_icon.svg b/assets/icon/cursor_icon.svg deleted file mode 100644 index 614e7fb8..00000000 --- a/assets/icon/cursor_icon.svg +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a2aa913b..7185ab75 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -115,23 +115,23 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89 + device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 - file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be + file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_localization: 72299fb6cb4e51cae587bd953ed0b958040b71e6 - image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a - integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e - launch_review_latest: 28e236fc255d91ec1430c39f951c4db1398c79c1 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + flutter_localization: f43b18844a2b3d2c71fd64f04ffd6b1e64dd54d4 + image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 + integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 + launch_review_latest: d405bc299b841153fc24f566d145b67a49c5245b + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 SDWebImage: dfe95b2466a9823cf9f0c6d01217c06550d7b29a - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 - sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe PODFILE CHECKSUM: aa9c826e174e713c4dad1b0d2110be4d87591fc5 diff --git a/lib/core/commands/command_painter.dart b/lib/core/commands/command_painter.dart index 759a56af..ebc79e26 100644 --- a/lib/core/commands/command_painter.dart +++ b/lib/core/commands/command_painter.dart @@ -3,9 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:paintroid/core/commands/command_manager/command_manager.dart'; import 'package:paintroid/core/commands/command_manager/command_manager_provider.dart'; import 'package:paintroid/core/enums/tool_types.dart'; -import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; import 'package:paintroid/core/providers/state/paint_provider.dart'; import 'package:paintroid/core/providers/state/toolbox_state_provider.dart'; +import 'package:paintroid/core/tools/implementation/cursor_tool.dart'; import 'package:paintroid/core/tools/implementation/shapes_tool/shapes_tool.dart'; import 'package:paintroid/core/tools/line_tool/line_tool.dart'; import 'package:paintroid/core/tools/tool.dart'; @@ -24,7 +24,7 @@ class CommandPainter extends CustomPainter { if (currentTool.type != ToolType.SHAPES) { canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); } - print('PAIJT'); + switch (currentTool.type) { case ToolType.LINE: _drawGhostPathsAndVertices(canvas, currentTool as LineTool); @@ -35,9 +35,10 @@ class CommandPainter extends CustomPainter { ..drawGuides(canvas); break; case ToolType.CURSOR: - print('in case'); - ref.read(canvasStateProvider.notifier).updateCursorPosition(currentTool.iconPosition!); - print(currentTool.iconPosition); + commandManager.executeLastCommand(canvas); + (currentTool as CursorTool) + .drawCursorIcon(canvas, ref.read(paintProvider)); + break; default: commandManager.executeLastCommand(canvas); break; diff --git a/lib/core/providers/object/tools/cursor_tool_provider.dart b/lib/core/providers/object/tools/cursor_tool_provider.dart index c78854ec..89c18bc0 100644 --- a/lib/core/providers/object/tools/cursor_tool_provider.dart +++ b/lib/core/providers/object/tools/cursor_tool_provider.dart @@ -1,5 +1,7 @@ +import 'dart:ui'; -import 'package:paintroid/core/tools/cursor_tool/cursor_tool.dart'; +import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; +import 'package:paintroid/core/tools/implementation/cursor_tool.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:paintroid/core/commands/command_factory/command_factory_provider.dart'; @@ -13,10 +15,12 @@ part 'cursor_tool_provider.g.dart'; class CursorToolProvider extends _$CursorToolProvider { @override CursorTool build() { + final canvasCenter = ref.read(canvasStateProvider).size.center(Offset.zero); return CursorTool( commandManager: ref.watch(commandManagerProvider), commandFactory: ref.watch(commandFactoryProvider), graphicFactory: ref.watch(graphicFactoryProvider), + canvasCenter: canvasCenter, type: ToolType.CURSOR, ); } diff --git a/lib/core/providers/object/tools/cursor_tool_provider.g.dart b/lib/core/providers/object/tools/cursor_tool_provider.g.dart index 2a1d3df2..21d3f0f9 100644 --- a/lib/core/providers/object/tools/cursor_tool_provider.g.dart +++ b/lib/core/providers/object/tools/cursor_tool_provider.g.dart @@ -7,7 +7,7 @@ part of 'cursor_tool_provider.dart'; // ************************************************************************** String _$cursorToolProviderHash() => - r'c72647c93c396424e71f321ffa01760e8da6a522'; + r'3d71164af06a30920e54814e04a468ce625909f1'; /// See also [CursorToolProvider]. @ProviderFor(CursorToolProvider) diff --git a/lib/core/providers/state/canvas_state_data.dart b/lib/core/providers/state/canvas_state_data.dart index 17f928f1..f5a0cbe2 100644 --- a/lib/core/providers/state/canvas_state_data.dart +++ b/lib/core/providers/state/canvas_state_data.dart @@ -16,6 +16,5 @@ class CanvasStateData with _$CanvasStateData { required Size size, required CommandManager commandManager, required GraphicFactory graphicFactory, - required Offset cursorPosition, }) = _CanvasStateData; } diff --git a/lib/core/providers/state/canvas_state_data.freezed.dart b/lib/core/providers/state/canvas_state_data.freezed.dart index 2d78eade..e90275db 100644 --- a/lib/core/providers/state/canvas_state_data.freezed.dart +++ b/lib/core/providers/state/canvas_state_data.freezed.dart @@ -21,7 +21,6 @@ mixin _$CanvasStateData { ui.Size get size => throw _privateConstructorUsedError; CommandManager get commandManager => throw _privateConstructorUsedError; GraphicFactory get graphicFactory => throw _privateConstructorUsedError; - ui.Offset get cursorPosition => throw _privateConstructorUsedError; /// Create a copy of CanvasStateData /// with the given fields replaced by the non-null parameter values. @@ -41,8 +40,7 @@ abstract class $CanvasStateDataCopyWith<$Res> { ui.Image? cachedImage, ui.Size size, CommandManager commandManager, - GraphicFactory graphicFactory, - ui.Offset cursorPosition}); + GraphicFactory graphicFactory}); } /// @nodoc @@ -65,7 +63,6 @@ class _$CanvasStateDataCopyWithImpl<$Res, $Val extends CanvasStateData> Object? size = null, Object? commandManager = null, Object? graphicFactory = null, - Object? cursorPosition = null, }) { return _then(_value.copyWith( backgroundImage: freezed == backgroundImage @@ -88,10 +85,6 @@ class _$CanvasStateDataCopyWithImpl<$Res, $Val extends CanvasStateData> ? _value.graphicFactory : graphicFactory // ignore: cast_nullable_to_non_nullable as GraphicFactory, - cursorPosition: null == cursorPosition - ? _value.cursorPosition - : cursorPosition // ignore: cast_nullable_to_non_nullable - as ui.Offset, ) as $Val); } } @@ -109,8 +102,7 @@ abstract class _$$CanvasStateDataImplCopyWith<$Res> ui.Image? cachedImage, ui.Size size, CommandManager commandManager, - GraphicFactory graphicFactory, - ui.Offset cursorPosition}); + GraphicFactory graphicFactory}); } /// @nodoc @@ -131,7 +123,6 @@ class __$$CanvasStateDataImplCopyWithImpl<$Res> Object? size = null, Object? commandManager = null, Object? graphicFactory = null, - Object? cursorPosition = null, }) { return _then(_$CanvasStateDataImpl( backgroundImage: freezed == backgroundImage @@ -154,10 +145,6 @@ class __$$CanvasStateDataImplCopyWithImpl<$Res> ? _value.graphicFactory : graphicFactory // ignore: cast_nullable_to_non_nullable as GraphicFactory, - cursorPosition: null == cursorPosition - ? _value.cursorPosition - : cursorPosition // ignore: cast_nullable_to_non_nullable - as ui.Offset, )); } } @@ -170,8 +157,7 @@ class _$CanvasStateDataImpl implements _CanvasStateData { this.cachedImage, required this.size, required this.commandManager, - required this.graphicFactory, - required this.cursorPosition}); + required this.graphicFactory}); @override final ui.Image? backgroundImage; @@ -183,12 +169,10 @@ class _$CanvasStateDataImpl implements _CanvasStateData { final CommandManager commandManager; @override final GraphicFactory graphicFactory; - @override - final ui.Offset cursorPosition; @override String toString() { - return 'CanvasStateData(backgroundImage: $backgroundImage, cachedImage: $cachedImage, size: $size, commandManager: $commandManager, graphicFactory: $graphicFactory, cursorPosition: $cursorPosition)'; + return 'CanvasStateData(backgroundImage: $backgroundImage, cachedImage: $cachedImage, size: $size, commandManager: $commandManager, graphicFactory: $graphicFactory)'; } @override @@ -204,14 +188,12 @@ class _$CanvasStateDataImpl implements _CanvasStateData { (identical(other.commandManager, commandManager) || other.commandManager == commandManager) && (identical(other.graphicFactory, graphicFactory) || - other.graphicFactory == graphicFactory) && - (identical(other.cursorPosition, cursorPosition) || - other.cursorPosition == cursorPosition)); + other.graphicFactory == graphicFactory)); } @override int get hashCode => Object.hash(runtimeType, backgroundImage, cachedImage, - size, commandManager, graphicFactory, cursorPosition); + size, commandManager, graphicFactory); /// Create a copy of CanvasStateData /// with the given fields replaced by the non-null parameter values. @@ -229,8 +211,7 @@ abstract class _CanvasStateData implements CanvasStateData { final ui.Image? cachedImage, required final ui.Size size, required final CommandManager commandManager, - required final GraphicFactory graphicFactory, - required final ui.Offset cursorPosition}) = _$CanvasStateDataImpl; + required final GraphicFactory graphicFactory}) = _$CanvasStateDataImpl; @override ui.Image? get backgroundImage; @@ -242,8 +223,6 @@ abstract class _CanvasStateData implements CanvasStateData { CommandManager get commandManager; @override GraphicFactory get graphicFactory; - @override - ui.Offset get cursorPosition; /// Create a copy of CanvasStateData /// with the given fields replaced by the non-null parameter values. diff --git a/lib/core/providers/state/canvas_state_provider.dart b/lib/core/providers/state/canvas_state_provider.dart index 9cdc88b5..272cbe1f 100644 --- a/lib/core/providers/state/canvas_state_provider.dart +++ b/lib/core/providers/state/canvas_state_provider.dart @@ -18,33 +18,27 @@ class CanvasStateProvider extends _$CanvasStateProvider { @override CanvasStateData build() { initialCanvasSize = ref.watch(IDeviceService.sizeProvider).when( - data: (size) => size, - error: (_, __) => widgets.WidgetsBinding.instance.platformDispatcher - .views.first.physicalSize, - loading: () => Size.zero, - ); + data: (size) => size, + error: (_, __) => widgets.WidgetsBinding.instance.platformDispatcher + .views.first.physicalSize, + loading: () => Size.zero, + ); return CanvasStateData( size: initialCanvasSize, commandManager: ref.watch(commandManagerProvider), graphicFactory: ref.watch(graphicFactoryProvider), - cursorPosition: Offset(initialCanvasSize.width / 2, initialCanvasSize.height / 2), // Initialize at center ); } - void updateCursorPosition(Offset position) { - print('updated'); - state = state.copyWith(cursorPosition: position); - } - void setBackgroundImage(Image image) => state = state.copyWith( - backgroundImage: image, - size: Size(image.width.toDouble(), image.height.toDouble()), - ); + backgroundImage: image, + size: Size(image.width.toDouble(), image.height.toDouble()), + ); void clearBackgroundImageAndResetDimensions() => state = state.copyWith( - backgroundImage: null, - size: initialCanvasSize, - ); + backgroundImage: null, + size: initialCanvasSize, + ); Future updateCachedImage() async { final recorder = state.graphicFactory.createPictureRecorder(); diff --git a/lib/core/providers/state/canvas_state_provider.g.dart b/lib/core/providers/state/canvas_state_provider.g.dart index d013af1f..377d5217 100644 --- a/lib/core/providers/state/canvas_state_provider.g.dart +++ b/lib/core/providers/state/canvas_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'canvas_state_provider.dart'; // ************************************************************************** String _$canvasStateProviderHash() => - r'dc37460b6736868558f365325aa70bbaaa738408'; + r'679bba9b579d049bcfbf4cc5231ae14a2d5baeab'; /// See also [CanvasStateProvider]. @ProviderFor(CanvasStateProvider) diff --git a/lib/core/providers/state/toolbox_state_provider.dart b/lib/core/providers/state/toolbox_state_provider.dart index a19cad2b..30a6c09a 100644 --- a/lib/core/providers/state/toolbox_state_provider.dart +++ b/lib/core/providers/state/toolbox_state_provider.dart @@ -88,11 +88,4 @@ class ToolBoxStateProvider extends _$ToolBoxStateProvider { ref.read(paintProvider.notifier).updateBlendModeByToolType(data.type); ToastUtils.showShortToast(message: data.name); } - - void updateIconPosition(Offset position) { - // Create a new tool with the updated position - final updatedTool = state.currentTool; - // Update the state with the new tool - state = state.copyWith(currentTool: updatedTool); - } } diff --git a/lib/core/providers/state/toolbox_state_provider.g.dart b/lib/core/providers/state/toolbox_state_provider.g.dart index d0e28b9a..a33252ab 100644 --- a/lib/core/providers/state/toolbox_state_provider.g.dart +++ b/lib/core/providers/state/toolbox_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'toolbox_state_provider.dart'; // ************************************************************************** String _$toolBoxStateProviderHash() => - r'27d5cd418224f6d4f51b0662e5a02c5dd6544f01'; + r'4c6d05e9bbdf692e3f872050342e842d04ad2f9e'; /// See also [ToolBoxStateProvider]. @ProviderFor(ToolBoxStateProvider) diff --git a/lib/core/tools/cursor_tool/cursor_icon.dart b/lib/core/tools/cursor_tool/cursor_icon.dart deleted file mode 100644 index 4ddd9f30..00000000 --- a/lib/core/tools/cursor_tool/cursor_icon.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:paintroid/ui/shared/icon_svg.dart'; - -class CursorIcon extends StatelessWidget { - final Color drawColor; - final double size; - - const CursorIcon({ - required this.drawColor, - this.size = 320, - }) ; - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () => print('tapped'), - child: SizedBox( - width: size, - height: size, - child: IconSvg( - path: 'assets/icon/cursor_icon.svg', - height: size, - width: size, - ), - ), - ); - } -} diff --git a/lib/core/tools/cursor_tool/cursor_tool.dart b/lib/core/tools/cursor_tool/cursor_tool.dart deleted file mode 100644 index 85b59d1f..00000000 --- a/lib/core/tools/cursor_tool/cursor_tool.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; - -import 'package:paintroid/core/commands/graphic_factory/graphic_factory.dart'; -import 'package:paintroid/core/commands/path_with_action_history.dart'; -import 'package:paintroid/core/tools/cursor_tool/cursor_icon.dart'; -import 'package:paintroid/core/tools/tool.dart'; - -class CursorTool extends Tool { - final GraphicFactory graphicFactory; - - @visibleForTesting - late PathWithActionHistory pathToDraw; - - CursorTool({ - required super.commandFactory, - required super.commandManager, - required this.graphicFactory, - required super.type, - super.hasAddFunctionality = false, - super.hasFinalizeFunctionality = false, - super.icon = const CursorIcon(drawColor: Color(234234)), - super.iconPosition = const Offset(0,0), - }); - - @override - void onDown(Offset point, Paint paint) { - print("On Down was pressed"); - super.iconPosition = point; - } - - @override - void onDrag(Offset point, Paint paint) { - print("On Drag was pressed"); - super.iconPosition = point; - } - - @override - void onUp(Offset point, Paint paint) { - super.iconPosition = point; - } - - @override - void onCancel() { - } - - @override - void onCheckmark(Paint paint) {} - - @override - void onPlus() {} - - @override - void onRedo() { - commandManager.redo(); - } - - @override - void onUndo() { - commandManager.undo(); - } -} diff --git a/lib/core/tools/implementation/cursor_tool.dart b/lib/core/tools/implementation/cursor_tool.dart new file mode 100644 index 00000000..2466cb70 --- /dev/null +++ b/lib/core/tools/implementation/cursor_tool.dart @@ -0,0 +1,161 @@ +import 'package:flutter/material.dart'; +import 'package:paintroid/core/tools/implementation/brush_tool.dart'; + +class CursorTool extends BrushTool { + static const double _tapTolerance = 10.0; + static const double _circleRadius = 40.0; + static const double _crossLength = 90.0; + static const double _crossThickness = 10.0; + static const double _strokeWidth = 10.0; + + final Offset canvasCenter; + Offset lastPoint = Offset.zero; + bool isActive = false; + + bool _isCurrentlyDrawing = false; + Offset? _initialTouchPoint; + Offset? _initialCursorPosition; + bool _hasDragged = false; + + CursorTool({ + required super.commandFactory, + required super.commandManager, + required super.graphicFactory, + required this.canvasCenter, + required super.type, + }) { + lastPoint = canvasCenter; + } + + void toggleActive() => isActive = !isActive; + + void setCursorPosition(Offset position) => lastPoint = position; + + @override + void onDown(Offset point, Paint paint) { + _initializeTouch(point); + + if (isActive) { + super.onDown(lastPoint, paint); + _isCurrentlyDrawing = true; + } + } + + @override + void onDrag(Offset point, Paint paint) { + _updateDragState(point); + _updateCursorPosition(point); + + if (isActive && _isCurrentlyDrawing) { + super.onDrag(lastPoint, paint); + } + } + + @override + void onUp(Offset point, Paint paint) { + _updateCursorPosition(point); + + if (_isTap()) { + _handleTap(); + return; + } + + if (isActive && _isCurrentlyDrawing) { + super.onUp(lastPoint, paint); + } + + _finalizeDraw(); + } + + void drawCursorIcon(Canvas canvas, Paint paint) { + final cursorColor = isActive ? Colors.red : Colors.black; + final circlePaint = _createCirclePaint(cursorColor); + final crossPaint = _createCrossPaint(cursorColor); + + canvas.drawCircle(lastPoint, _circleRadius, circlePaint); + _drawCrossLines(canvas, crossPaint); + } + + void _initializeTouch(Offset point) { + _initialTouchPoint = point; + _initialCursorPosition = lastPoint; + _hasDragged = false; + _isCurrentlyDrawing = false; + } + + void _updateDragState(Offset point) { + if (_initialTouchPoint != null) { + final distance = (point - _initialTouchPoint!).distance; + if (distance > _tapTolerance) _hasDragged = true; + } + } + + void _updateCursorPosition(Offset point) { + if (_initialTouchPoint != null && _initialCursorPosition != null) { + final delta = point - _initialTouchPoint!; + lastPoint = _initialCursorPosition! + delta; + } + } + + bool _isTap() => !_hasDragged; + + void _handleTap() { + toggleActive(); + if (!isActive && _isCurrentlyDrawing) { + _isCurrentlyDrawing = false; + _resetTracking(); + } + } + + void _finalizeDraw() { + _isCurrentlyDrawing = false; + _resetTracking(); + } + + void _resetTracking() { + _initialTouchPoint = null; + _initialCursorPosition = null; + _hasDragged = false; + } + + Paint _createCirclePaint(Color color) => Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = _strokeWidth + ..isAntiAlias = true; + + Paint _createCrossPaint(Color color) => Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = _crossThickness + ..strokeCap = StrokeCap.round + ..isAntiAlias = true; + + void _drawCrossLines(Canvas canvas, Paint paint) { + final crossStart = _circleRadius; + final crossEnd = crossStart + _crossLength; + + final lines = [ + ( + Offset(lastPoint.dx, lastPoint.dy - crossEnd), + Offset(lastPoint.dx, lastPoint.dy - crossStart) + ), + ( + Offset(lastPoint.dx, lastPoint.dy + crossStart), + Offset(lastPoint.dx, lastPoint.dy + crossEnd) + ), + ( + Offset(lastPoint.dx - crossEnd, lastPoint.dy), + Offset(lastPoint.dx - crossStart, lastPoint.dy) + ), + ( + Offset(lastPoint.dx + crossStart, lastPoint.dy), + Offset(lastPoint.dx + crossEnd, lastPoint.dy) + ), + ]; + + for (final (start, end) in lines) { + canvas.drawLine(start, end, paint); + } + } +} diff --git a/lib/core/tools/tool.dart b/lib/core/tools/tool.dart index c2f09cbf..079e27fe 100644 --- a/lib/core/tools/tool.dart +++ b/lib/core/tools/tool.dart @@ -4,7 +4,6 @@ import 'package:flutter/cupertino.dart'; import 'package:paintroid/core/commands/command_factory/command_factory.dart'; import 'package:paintroid/core/commands/command_manager/command_manager.dart'; import 'package:paintroid/core/enums/tool_types.dart'; -import 'package:paintroid/core/tools/cursor_tool/cursor_icon.dart'; abstract class Tool { final ToolType type; @@ -12,17 +11,13 @@ abstract class Tool { final CommandFactory commandFactory; final bool hasAddFunctionality; final bool hasFinalizeFunctionality; - CursorIcon? icon; - Offset? iconPosition; - Tool({ + Tool({ required this.commandManager, required this.commandFactory, required this.type, required this.hasAddFunctionality, required this.hasFinalizeFunctionality, - this.icon, - this.iconPosition, }); void onDown(Offset point, Paint paint); diff --git a/lib/ui/pages/workspace_page/components/drawing_surface/canvas_painter.dart b/lib/ui/pages/workspace_page/components/drawing_surface/canvas_painter.dart index ef5ab047..0d6478cc 100644 --- a/lib/ui/pages/workspace_page/components/drawing_surface/canvas_painter.dart +++ b/lib/ui/pages/workspace_page/components/drawing_surface/canvas_painter.dart @@ -5,7 +5,6 @@ import 'package:paintroid/core/commands/command_painter.dart'; import 'package:paintroid/core/providers/object/canvas_painter_provider.dart'; import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; import 'package:paintroid/core/providers/state/paint_provider.dart'; -import 'package:paintroid/core/providers/state/toolbox_state_provider.dart'; import 'package:paintroid/core/utils/widget_identifier.dart'; import 'package:paintroid/ui/pages/workspace_page/components/drawing_surface/checkerboard_pattern.dart'; @@ -15,36 +14,20 @@ class CanvasPainter extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final size = ref.watch(canvasStateProvider.select((state) => state.size)); - final currentTool = ref.read(toolBoxStateProvider).currentTool; - final iconPosition = ref.watch( - canvasStateProvider.select((state) => state.cursorPosition) - ); - return Stack( - children: [ - Container( - key: const ValueKey(WidgetIdentifier.canvasPainter), - width: size.width, - height: size.height, - foregroundDecoration: const BoxDecoration( - border: Border.fromBorderSide(BorderSide(width: 0.5)), - ), - child: Stack( - fit: StackFit.expand, - children: [ - BackgroundLayer(), - PaintingLayer(), - if (currentTool.icon != null) - Positioned( - left: iconPosition!.dx - 160, - top: iconPosition.dy - 160, - child: currentTool.icon!, - ), - ], - ), - ), - - - ], + return Container( + key: const ValueKey(WidgetIdentifier.canvasPainter), + width: size.width, + height: size.height, + foregroundDecoration: const BoxDecoration( + border: Border.fromBorderSide(BorderSide(width: 0.5)), + ), + child: Stack( + fit: StackFit.expand, + children: [ + BackgroundLayer(), + PaintingLayer(), + ], + ), ); } } diff --git a/test/integration/cursor_tool_test.dart b/test/integration/cursor_tool_test.dart new file mode 100644 index 00000000..6c2d610f --- /dev/null +++ b/test/integration/cursor_tool_test.dart @@ -0,0 +1,335 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:paintroid/app.dart'; +import 'package:paintroid/core/tools/tool_data.dart'; +import 'package:paintroid/core/utils/color_utils.dart'; + +import '../utils/canvas_positions.dart'; +import '../utils/ui_interaction.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + const String testIDStr = String.fromEnvironment('id', defaultValue: '-1'); + final testID = int.tryParse(testIDStr) ?? testIDStr; + + late Widget sut; + + setUp(() async { + sut = ProviderScope( + child: App( + showOnboardingPage: false, + ), + ); + }); + + if (testID == -1 || testID == 0) { + testWidgets('[CURSOR_TOOL]: cursor positioning without drawing', + (WidgetTester tester) async { + UIInteraction.initialize(tester); + await tester.pumpWidget(sut); + await UIInteraction.createNewImage(); + UIInteraction.setColor(Colors.black); + await UIInteraction.selectTool(ToolData.CURSOR.name); + + await UIInteraction.dragFromTo( + CanvasPosition.topLeft, + CanvasPosition.bottomRight, + ); + + var color = await UIInteraction.getPixelColor( + CanvasPosition.centerX, + CanvasPosition.centerY, + ); + expect(color.toValue(), Colors.transparent.toValue()); + }); + } + + if (testID == -1 || testID == 1) { + testWidgets('[CURSOR_TOOL]: toggle cursor active with tap', + (WidgetTester tester) async { + UIInteraction.initialize(tester); + await tester.pumpWidget(sut); + await UIInteraction.createNewImage(); + UIInteraction.setColor(Colors.black); + await UIInteraction.selectTool(ToolData.CURSOR.name); + + await UIInteraction.tapAt(CanvasPosition.center); + + await UIInteraction.dragFromTo( + CanvasPosition.topLeft, + CanvasPosition.bottomRight, + ); + + var color = await UIInteraction.getPixelColor( + CanvasPosition.centerX, + CanvasPosition.centerY, + ); + expect(color.toValue(), Colors.black.toValue()); + }); + } + + if (testID == -1 || testID == 2) { + testWidgets('[CURSOR_TOOL]: drawing when cursor is active', + (WidgetTester tester) async { + UIInteraction.initialize(tester); + await tester.pumpWidget(sut); + await UIInteraction.createNewImage(); + UIInteraction.setColor(Colors.black); + await UIInteraction.selectTool(ToolData.CURSOR.name); + + await UIInteraction.tapAt(CanvasPosition.center); + + await UIInteraction.dragFromTo( + CanvasPosition.topLeft, + CanvasPosition.bottomRight, + ); + + var color = await UIInteraction.getPixelColor( + CanvasPosition.centerX, + CanvasPosition.centerY, + ); + expect(color.toValue(), Colors.black.toValue()); + }); + } + + if (testID == -1 || testID == 3) { + testWidgets('[CURSOR_TOOL]: toggle cursor off stops drawing', + (WidgetTester tester) async { + UIInteraction.initialize(tester); + await tester.pumpWidget(sut); + await UIInteraction.createNewImage(); + UIInteraction.setColor(Colors.black); + await UIInteraction.selectTool(ToolData.CURSOR.name); + + await UIInteraction.tapAt(CanvasPosition.center); + + await UIInteraction.dragFromTo( + CanvasPosition.topLeft, + CanvasPosition.centerLeft, + ); + + var color = await UIInteraction.getPixelColor( + CanvasPosition.centerX, + CanvasPosition.centerY, + ); + expect(color.toValue(), Colors.black.toValue()); + + await UIInteraction.tapAt(CanvasPosition.center); + + await UIInteraction.dragFromTo( + CanvasPosition.centerRight, + CanvasPosition.bottomRight, + ); + + color = await UIInteraction.getPixelColor( + CanvasPosition.right, + CanvasPosition.centerY, + ); + expect(color.toValue(), Colors.transparent.toValue()); + }); + } + + if (testID == -1 || testID == 4) { + testWidgets('[CURSOR_TOOL]: cursor position follows drag movement', + (WidgetTester tester) async { + UIInteraction.initialize(tester); + await tester.pumpWidget(sut); + await UIInteraction.createNewImage(); + UIInteraction.setColor(Colors.black); + await UIInteraction.selectTool(ToolData.CURSOR.name); + + await UIInteraction.tapAt(CanvasPosition.center); + + await UIInteraction.dragFromTo( + CanvasPosition.center, + CanvasPosition.topLeft, + ); + + await UIInteraction.dragFromTo( + CanvasPosition.topLeft, + CanvasPosition.bottomRight, + ); + + var color = await UIInteraction.getPixelColor( + CanvasPosition.centerX, + CanvasPosition.centerY, + ); + expect(color.toValue(), Colors.black.toValue()); + }); + } + + if (testID == -1 || testID == 5) { + testWidgets('[CURSOR_TOOL]: undo and redo with cursor drawing', + (WidgetTester tester) async { + UIInteraction.initialize(tester); + await tester.pumpWidget(sut); + await UIInteraction.createNewImage(); + UIInteraction.setColor(Colors.black); + await UIInteraction.selectTool(ToolData.CURSOR.name); + + await UIInteraction.tapAt(CanvasPosition.center); + + await UIInteraction.dragFromTo( + CanvasPosition.topLeft, + CanvasPosition.bottomRight, + ); + + var color = await UIInteraction.getPixelColor( + CanvasPosition.centerX, + CanvasPosition.centerY, + ); + expect(color.toValue(), Colors.black.toValue()); + + await UIInteraction.clickUndo(); + + color = await UIInteraction.getPixelColor( + CanvasPosition.centerX, + CanvasPosition.centerY, + ); + expect(color.toValue(), Colors.transparent.toValue()); + + await UIInteraction.clickRedo(); + + color = await UIInteraction.getPixelColor( + CanvasPosition.centerX, + CanvasPosition.centerY, + ); + expect(color.toValue(), Colors.black.toValue()); + }); + } + + if (testID == -1 || testID == 6) { + testWidgets('[CURSOR_TOOL]: multiple cursor movements and drawings', + (WidgetTester tester) async { + UIInteraction.initialize(tester); + await tester.pumpWidget(sut); + await UIInteraction.createNewImage(); + UIInteraction.setColor(Colors.black); + await UIInteraction.selectTool(ToolData.CURSOR.name); + + await UIInteraction.tapAt(CanvasPosition.center); + + await UIInteraction.dragFromTo( + CanvasPosition.center, + CanvasPosition.topLeft, + ); + await UIInteraction.dragFromTo( + CanvasPosition.topLeft, + CanvasPosition.topRight, + ); + + await UIInteraction.dragFromTo( + CanvasPosition.topRight, + CanvasPosition.bottomLeft, + ); + await UIInteraction.dragFromTo( + CanvasPosition.bottomLeft, + CanvasPosition.bottomRight, + ); + + var color = await UIInteraction.getPixelColor( + CanvasPosition.right, + CanvasPosition.top, + ); + expect(color.toValue(), Colors.black.toValue()); + + color = await UIInteraction.getPixelColor( + CanvasPosition.right, + CanvasPosition.bottom, + ); + expect(color.toValue(), Colors.black.toValue()); + }); + } + + if (testID == -1 || testID == 7) { + testWidgets('[CURSOR_TOOL]: drawing with different colors', + (WidgetTester tester) async { + UIInteraction.initialize(tester); + await tester.pumpWidget(sut); + await UIInteraction.createNewImage(); + await UIInteraction.selectTool(ToolData.CURSOR.name); + + UIInteraction.setColor(Colors.black); + + await UIInteraction.tapAt(CanvasPosition.center); + + await UIInteraction.dragFromTo( + CanvasPosition.topLeft, + CanvasPosition.bottomRight, + ); + + await UIInteraction.tapAt(CanvasPosition.center); + + await UIInteraction.dragFromTo( + CanvasPosition.bottomRight, + CanvasPosition.topLeft, + ); + + var color = await UIInteraction.getPixelColor( + CanvasPosition.centerX, + CanvasPosition.centerY, + ); + expect(color.toValue(), Colors.black.toValue()); + + UIInteraction.setColor(Colors.red); + + await UIInteraction.tapAt(CanvasPosition.center); + + await UIInteraction.dragFromTo( + CanvasPosition.topRight, + CanvasPosition.bottomLeft, + ); + + color = await UIInteraction.getPixelColor( + CanvasPosition.centerX, + CanvasPosition.centerY, + ); + expect(color.toValue(), Colors.red.toValue()); + }); + } + + if (testID == -1 || testID == 8) { + testWidgets('[CURSOR_TOOL]: inactive cursor does not interfere with touch', + (WidgetTester tester) async { + UIInteraction.initialize(tester); + await tester.pumpWidget(sut); + await UIInteraction.createNewImage(); + UIInteraction.setColor(Colors.black); + await UIInteraction.selectTool(ToolData.CURSOR.name); + + await UIInteraction.dragFromTo( + CanvasPosition.topLeft, + CanvasPosition.bottomRight, + ); + await UIInteraction.dragFromTo( + CanvasPosition.topRight, + CanvasPosition.bottomLeft, + ); + await UIInteraction.dragFromTo( + CanvasPosition.centerLeft, + CanvasPosition.centerRight, + ); + + var color = await UIInteraction.getPixelColor( + CanvasPosition.centerX, + CanvasPosition.centerY, + ); + expect(color.toValue(), Colors.transparent.toValue()); + + color = await UIInteraction.getPixelColor( + CanvasPosition.left, + CanvasPosition.centerY, + ); + expect(color.toValue(), Colors.transparent.toValue()); + + color = await UIInteraction.getPixelColor( + CanvasPosition.right, + CanvasPosition.centerY, + ); + expect(color.toValue(), Colors.transparent.toValue()); + }); + } +} diff --git a/test/unit/tools/cursor_tool_test.dart b/test/unit/tools/cursor_tool_test.dart new file mode 100644 index 00000000..2c875288 --- /dev/null +++ b/test/unit/tools/cursor_tool_test.dart @@ -0,0 +1,215 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:paintroid/core/commands/command_factory/command_factory.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/path_command.dart'; +import 'package:paintroid/core/commands/command_manager/command_manager.dart'; +import 'package:paintroid/core/commands/graphic_factory/graphic_factory.dart'; +import 'package:paintroid/core/commands/path_with_action_history.dart'; +import 'package:paintroid/core/enums/tool_types.dart'; +import 'package:paintroid/core/tools/implementation/cursor_tool.dart'; + +void main() { + late CursorTool sut; + + const Offset pointA = Offset(100, 100); + const Offset pointB = Offset(200, 200); + const Offset canvasCenter = Offset(150, 150); + + Paint paint = Paint(); + + setUp(() { + sut = CursorTool( + commandFactory: const CommandFactory(), + commandManager: CommandManager(), + graphicFactory: const GraphicFactory(), + canvasCenter: canvasCenter, + type: ToolType.CURSOR, + ); + }); + + group('Initialization', () { + test('Should initialize with cursor at canvas center', () { + expect(sut.lastPoint, canvasCenter); + }); + + test('Should initialize with inactive state', () { + expect(sut.isActive, false); + }); + + test('Should return CURSOR as ToolType', () { + expect(sut.type, ToolType.CURSOR); + }); + }); + + group('Cursor positioning', () { + test('Should set cursor position correctly', () { + sut.setCursorPosition(pointA); + expect(sut.lastPoint, pointA); + }); + + test('Should update cursor position when dragging', () { + sut.onDown(pointA, paint); + sut.onDrag(pointB, paint); + + final expectedPosition = canvasCenter + (pointB - pointA); + expect(sut.lastPoint, expectedPosition); + }); + }); + + group('Active state management', () { + test('Should toggle active state', () { + expect(sut.isActive, false); + sut.toggleActive(); + expect(sut.isActive, true); + sut.toggleActive(); + expect(sut.isActive, false); + }); + + test('Should toggle active state on tap', () { + expect(sut.isActive, false); + sut.onDown(pointA, paint); + sut.onUp(pointA, paint); + expect(sut.isActive, true); + }); + + test('Should deactivate when tapping while active', () { + sut.toggleActive(); + expect(sut.isActive, true); + + sut.onDown(pointA, paint); + sut.onUp(pointA, paint); + expect(sut.isActive, false); + }); + }); + + group('Drawing behavior when inactive', () { + test('Should not create PathCommand when inactive on down', () { + expect(sut.commandManager.undoStack.isEmpty, true); + sut.onDown(pointA, paint); + expect(sut.commandManager.undoStack.isEmpty, true); + }); + + test('Should not create PathCommand when inactive on drag', () { + expect(sut.commandManager.undoStack.isEmpty, true); + sut.onDown(pointA, paint); + sut.onDrag(pointB, paint); + expect(sut.commandManager.undoStack.isEmpty, true); + }); + + test('Should not create PathCommand when inactive on up', () { + expect(sut.commandManager.undoStack.isEmpty, true); + sut.onDown(pointA, paint); + sut.onDrag(pointB, paint); + sut.onUp(pointB, paint); + expect(sut.commandManager.undoStack.isEmpty, true); + }); + }); + + group('Drawing behavior when active', () { + setUp(() { + sut.toggleActive(); + }); + + test('Should create PathCommand when active on down', () { + expect(sut.commandManager.undoStack.isEmpty, true); + sut.onDown(pointA, paint); + expect(sut.commandManager.undoStack.first is PathCommand, true); + }); + + test('Should add MoveToAction when active on down', () { + expect(sut.commandManager.undoStack.isEmpty, true); + sut.onDown(pointA, paint); + final firstAction = (sut.commandManager.undoStack.first as PathCommand) + .path + .actions + .first; + expect(firstAction is MoveToAction, true); + }); + + test('Should add LineToAction when active on drag', () { + expect(sut.commandManager.undoStack.isEmpty, true); + sut.onDown(pointA, paint); + sut.onDrag(pointB, paint); + final lastAction = + (sut.commandManager.undoStack.first as PathCommand).path.actions.last; + expect(lastAction is LineToAction, true); + }); + + test('Should create new PathCommand after completing a stroke', () { + expect(sut.commandManager.undoStack.isEmpty, true); + + const dragOffset = Offset(20, 20); + sut.onDown(pointA, paint); + sut.onDrag(pointA + dragOffset, paint); + sut.onUp(pointA + dragOffset, paint); + expect(sut.commandManager.undoStack.length, 1); + + sut.onDown(pointB, paint); + sut.onDrag(pointB + dragOffset, paint); + sut.onUp(pointB + dragOffset, paint); + expect(sut.commandManager.undoStack.length, 2); + }); + }); + + group('Tap vs Drag detection', () { + test('Should detect tap when movement is within tolerance', () { + const smallOffset = Offset(5, 5); + + sut.onDown(pointA, paint); + sut.onDrag(pointA + smallOffset, paint); + sut.onUp(pointA + smallOffset, paint); + + expect(sut.isActive, true); + }); + + test('Should detect drag when movement exceeds tolerance', () { + const largeOffset = Offset(50, 50); + + sut.onDown(pointA, paint); + sut.onDrag(pointA + largeOffset, paint); + sut.onUp(pointA + largeOffset, paint); + + expect(sut.isActive, false); + }); + }); + + group('Cursor position tracking during drag', () { + test('Should maintain cursor position relative to initial touch', () { + final initialCursorPos = sut.lastPoint; + const touchPoint = Offset(100, 100); + const dragPoint = Offset(150, 150); + + sut.onDown(touchPoint, paint); + sut.onDrag(dragPoint, paint); + + final expectedCursorPos = initialCursorPos + (dragPoint - touchPoint); + expect(sut.lastPoint, expectedCursorPos); + }); + + test('Should update cursor position correctly through multiple drags', () { + final initialCursorPos = sut.lastPoint; + const touchPoint = Offset(100, 100); + const dragPoint1 = Offset(150, 150); + const dragPoint2 = Offset(200, 200); + + sut.onDown(touchPoint, paint); + sut.onDrag(dragPoint1, paint); + sut.onDrag(dragPoint2, paint); + + final expectedCursorPos = initialCursorPos + (dragPoint2 - touchPoint); + expect(sut.lastPoint, expectedCursorPos); + }); + }); + + group('State reset', () { + test('Should reset tracking state after completing gesture', () { + sut.onDown(pointA, paint); + sut.onDrag(pointB, paint); + sut.onUp(pointB, paint); + expect(sut.isActive, false); + }); + }); +} diff --git a/test/unit/workspace/render_image_for_export_test.dart b/test/unit/workspace/render_image_for_export_test.dart index d3aa1032..b3182919 100644 --- a/test/unit/workspace/render_image_for_export_test.dart +++ b/test/unit/workspace/render_image_for_export_test.dart @@ -37,7 +37,6 @@ class MockCanvasState1 extends CanvasStateProvider { commandManager: MockCommandManager(), graphicFactory: FakeGraphicFactory(MockCanvas(), MockCanvas(), MockCanvas(), Paint()), - cursorPosition: Offset(0, 0), ); } } @@ -50,9 +49,7 @@ class MockCanvasState2 extends CanvasStateProvider { commandManager: MockCommandManager(), graphicFactory: FakeGraphicFactory(MockCanvas(), MockCanvas(), MockCanvas(), Paint()), - cursorPosition: Offset(0, 0), ); - } } From 54c5e0f279229c92b6dddbbabdafb96b82c21487 Mon Sep 17 00:00:00 2001 From: Hasan Keskin Date: Wed, 20 Aug 2025 13:45:24 +0200 Subject: [PATCH 5/8] PAINTROID-790 fix undo and flutter analyze errors --- .../commands/command_factory/command_factory.dart | 7 ++++--- .../command_implementation/graphic/path_command.dart | 2 ++ .../commands/command_manager/command_manager.dart | 12 ++++++++++-- lib/core/tools/implementation/brush_tool.dart | 8 +++++++- lib/core/tools/implementation/cursor_tool.dart | 11 ++++++----- lib/core/tools/tool.dart | 2 +- .../components/drawing_surface/canvas_painter.dart | 2 +- 7 files changed, 31 insertions(+), 13 deletions(-) diff --git a/lib/core/commands/command_factory/command_factory.dart b/lib/core/commands/command_factory/command_factory.dart index e26bf3d0..049c3c46 100644 --- a/lib/core/commands/command_factory/command_factory.dart +++ b/lib/core/commands/command_factory/command_factory.dart @@ -12,9 +12,10 @@ class CommandFactory { PathCommand createPathCommand( PathWithActionHistory path, - Paint paint, - ) => - PathCommand(path, paint); + Paint paint, { + bool isCursor = false, + }) => + PathCommand(path, paint, isCursorPath: isCursor); LineCommand createLineCommand( PathWithActionHistory path, diff --git a/lib/core/commands/command_implementation/graphic/path_command.dart b/lib/core/commands/command_implementation/graphic/path_command.dart index a26fff85..ba0a20b0 100644 --- a/lib/core/commands/command_implementation/graphic/path_command.dart +++ b/lib/core/commands/command_implementation/graphic/path_command.dart @@ -17,12 +17,14 @@ part 'path_command.g.dart'; class PathCommand extends GraphicCommand { final String type; final int version; + final bool isCursorPath; PathCommand( this.path, super.paint, { this.type = SerializerType.PATH_COMMAND, int? version, + this.isCursorPath = false, }) : version = version ?? VersionStrategyManager.strategy.getPathCommandVersion(); diff --git a/lib/core/commands/command_manager/command_manager.dart b/lib/core/commands/command_manager/command_manager.dart index 9f33aa18..e1e65f24 100644 --- a/lib/core/commands/command_manager/command_manager.dart +++ b/lib/core/commands/command_manager/command_manager.dart @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:paintroid/core/commands/command_implementation/command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/path_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/circle_shape_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/square_shape_command.dart'; import 'package:paintroid/core/tools/line_tool/vertex.dart'; @@ -110,10 +111,17 @@ class CommandManager { return ToolData.SHAPES; } else if (command.runtimeType == CircleShapeCommand) { return ToolData.SHAPES; - } - else if (command.runtimeType == SprayCommand) { + } else if (command.runtimeType == CircleShapeCommand) { + return ToolData.SHAPES; + } else if (command.runtimeType == SprayCommand) { return ToolData.SPRAY; } else { + if (command.runtimeType == PathCommand) { + final pathCommand = command as PathCommand; + if (pathCommand.isCursorPath) { + return ToolData.CURSOR; + } + } return ToolData.BRUSH; } } diff --git a/lib/core/tools/implementation/brush_tool.dart b/lib/core/tools/implementation/brush_tool.dart index 3d7ade79..0c3e78fd 100644 --- a/lib/core/tools/implementation/brush_tool.dart +++ b/lib/core/tools/implementation/brush_tool.dart @@ -8,6 +8,7 @@ import 'package:paintroid/core/tools/tool.dart'; class BrushTool extends Tool { final GraphicFactory graphicFactory; + final bool isCursor; @visibleForTesting late PathWithActionHistory pathToDraw; @@ -17,6 +18,7 @@ class BrushTool extends Tool { required super.commandManager, required this.graphicFactory, required super.type, + this.isCursor = false, super.hasAddFunctionality = false, super.hasFinalizeFunctionality = false, }); @@ -26,7 +28,11 @@ class BrushTool extends Tool { pathToDraw = graphicFactory.createPathWithActionHistory() ..moveTo(point.dx, point.dy); Paint savedPaint = graphicFactory.copyPaint(paint); - final command = commandFactory.createPathCommand(pathToDraw, savedPaint); + final command = commandFactory.createPathCommand( + pathToDraw, + savedPaint, + isCursor: isCursor, + ); commandManager.addGraphicCommand(command); } diff --git a/lib/core/tools/implementation/cursor_tool.dart b/lib/core/tools/implementation/cursor_tool.dart index 2466cb70..b4b4333d 100644 --- a/lib/core/tools/implementation/cursor_tool.dart +++ b/lib/core/tools/implementation/cursor_tool.dart @@ -23,6 +23,7 @@ class CursorTool extends BrushTool { required super.graphicFactory, required this.canvasCenter, required super.type, + super.isCursor = true, }) { lastPoint = canvasCenter; } @@ -34,11 +35,6 @@ class CursorTool extends BrushTool { @override void onDown(Offset point, Paint paint) { _initializeTouch(point); - - if (isActive) { - super.onDown(lastPoint, paint); - _isCurrentlyDrawing = true; - } } @override @@ -46,6 +42,11 @@ class CursorTool extends BrushTool { _updateDragState(point); _updateCursorPosition(point); + if (isActive && !_isCurrentlyDrawing) { + super.onDown(lastPoint, paint); + _isCurrentlyDrawing = true; + } + if (isActive && _isCurrentlyDrawing) { super.onDrag(lastPoint, paint); } diff --git a/lib/core/tools/tool.dart b/lib/core/tools/tool.dart index 079e27fe..dc0b9999 100644 --- a/lib/core/tools/tool.dart +++ b/lib/core/tools/tool.dart @@ -12,7 +12,7 @@ abstract class Tool { final bool hasAddFunctionality; final bool hasFinalizeFunctionality; - Tool({ + const Tool({ required this.commandManager, required this.commandFactory, required this.type, diff --git a/lib/ui/pages/workspace_page/components/drawing_surface/canvas_painter.dart b/lib/ui/pages/workspace_page/components/drawing_surface/canvas_painter.dart index 0d6478cc..af7206b1 100644 --- a/lib/ui/pages/workspace_page/components/drawing_surface/canvas_painter.dart +++ b/lib/ui/pages/workspace_page/components/drawing_surface/canvas_painter.dart @@ -21,7 +21,7 @@ class CanvasPainter extends ConsumerWidget { foregroundDecoration: const BoxDecoration( border: Border.fromBorderSide(BorderSide(width: 0.5)), ), - child: Stack( + child: const Stack( fit: StackFit.expand, children: [ BackgroundLayer(), From 8d8a5376b0261339ddfa4c2170a7f8ef72fc65e9 Mon Sep 17 00:00:00 2001 From: Hasan Keskin Date: Sun, 24 Aug 2025 15:15:26 +0200 Subject: [PATCH 6/8] PAINTROID-790 make build files --- ios/Podfile.lock | 6 ------ .../command_implementation/graphic/path_command.g.dart | 2 ++ lib/core/providers/state/toolbox_state_provider.g.dart | 2 +- test/unit/tools/text_tool_test.mocks.dart | 7 +++++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7185ab75..a1bed615 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -42,8 +42,6 @@ PODS: - Flutter - integration_test (0.0.1): - Flutter - - launch_review_latest (0.0.1): - - Flutter - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): @@ -71,7 +69,6 @@ DEPENDENCIES: - flutter_localization (from `.symlinks/plugins/flutter_localization/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - - launch_review_latest (from `.symlinks/plugins/launch_review_latest/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) @@ -99,8 +96,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/image_picker_ios/ios" integration_test: :path: ".symlinks/plugins/integration_test/ios" - launch_review_latest: - :path: ".symlinks/plugins/launch_review_latest/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: @@ -123,7 +118,6 @@ SPEC CHECKSUMS: flutter_localization: f43b18844a2b3d2c71fd64f04ffd6b1e64dd54d4 image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 - launch_review_latest: d405bc299b841153fc24f566d145b67a49c5245b package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 diff --git a/lib/core/commands/command_implementation/graphic/path_command.g.dart b/lib/core/commands/command_implementation/graphic/path_command.g.dart index 5711dc2f..cb5748ad 100644 --- a/lib/core/commands/command_implementation/graphic/path_command.g.dart +++ b/lib/core/commands/command_implementation/graphic/path_command.g.dart @@ -12,6 +12,7 @@ PathCommand _$PathCommandFromJson(Map json) => PathCommand( const PaintConverter().fromJson(json['paint'] as Map), type: json['type'] as String? ?? SerializerType.PATH_COMMAND, version: (json['version'] as num?)?.toInt(), + isCursorPath: json['isCursorPath'] as bool? ?? false, ); Map _$PathCommandToJson(PathCommand instance) => @@ -19,5 +20,6 @@ Map _$PathCommandToJson(PathCommand instance) => 'paint': const PaintConverter().toJson(instance.paint), 'type': instance.type, 'version': instance.version, + 'isCursorPath': instance.isCursorPath, 'path': const PathWithActionHistoryConverter().toJson(instance.path), }; diff --git a/lib/core/providers/state/toolbox_state_provider.g.dart b/lib/core/providers/state/toolbox_state_provider.g.dart index 5d63e956..b9fcaafc 100644 --- a/lib/core/providers/state/toolbox_state_provider.g.dart +++ b/lib/core/providers/state/toolbox_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'toolbox_state_provider.dart'; // ************************************************************************** String _$toolBoxStateProviderHash() => - r'3866c79e5faeb54cf1a0da8cafa580a9d3971a4b'; + r'f0498599c68cada2437efabc81d5c099c4df39dd'; /// See also [ToolBoxStateProvider]. @ProviderFor(ToolBoxStateProvider) diff --git a/test/unit/tools/text_tool_test.mocks.dart b/test/unit/tools/text_tool_test.mocks.dart index c650d49e..2efe4d0a 100644 --- a/test/unit/tools/text_tool_test.mocks.dart +++ b/test/unit/tools/text_tool_test.mocks.dart @@ -413,8 +413,9 @@ class MockCommandFactory extends _i1.Mock implements _i17.CommandFactory { @override _i4.PathCommand createPathCommand( _i13.PathWithActionHistory? path, - _i11.Paint? paint, - ) => + _i11.Paint? paint, { + bool? isCursor = false, + }) => (super.noSuchMethod( Invocation.method( #createPathCommand, @@ -422,6 +423,7 @@ class MockCommandFactory extends _i1.Mock implements _i17.CommandFactory { path, paint, ], + {#isCursor: isCursor}, ), returnValue: _FakePathCommand_2( this, @@ -431,6 +433,7 @@ class MockCommandFactory extends _i1.Mock implements _i17.CommandFactory { path, paint, ], + {#isCursor: isCursor}, ), ), ) as _i4.PathCommand); From e0e792376a5fd6f30b2204c11535a0c549afc698 Mon Sep 17 00:00:00 2001 From: Hasan Keskin Date: Sun, 24 Aug 2025 15:24:47 +0200 Subject: [PATCH 7/8] PAINTROID-790 fix unit tests --- test/unit/tools/cursor_tool_test.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/unit/tools/cursor_tool_test.dart b/test/unit/tools/cursor_tool_test.dart index 2c875288..a6199d30 100644 --- a/test/unit/tools/cursor_tool_test.dart +++ b/test/unit/tools/cursor_tool_test.dart @@ -113,15 +113,17 @@ void main() { sut.toggleActive(); }); - test('Should create PathCommand when active on down', () { + test('Should create PathCommand when active on drag', () { expect(sut.commandManager.undoStack.isEmpty, true); sut.onDown(pointA, paint); + sut.onDrag(pointB, paint); expect(sut.commandManager.undoStack.first is PathCommand, true); }); - test('Should add MoveToAction when active on down', () { + test('Should add MoveToAction when active on drag', () { expect(sut.commandManager.undoStack.isEmpty, true); sut.onDown(pointA, paint); + sut.onDrag(pointB, paint); final firstAction = (sut.commandManager.undoStack.first as PathCommand) .path .actions From 94be0b656eb7142353cccdca893b9c1c4997b168 Mon Sep 17 00:00:00 2001 From: Hasan Keskin Date: Sun, 24 Aug 2025 16:17:06 +0200 Subject: [PATCH 8/8] PAINTROID-790 fix integration tests after logic change --- test/integration/cursor_tool_test.dart | 49 ++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/test/integration/cursor_tool_test.dart b/test/integration/cursor_tool_test.dart index 6c2d610f..f033d396 100644 --- a/test/integration/cursor_tool_test.dart +++ b/test/integration/cursor_tool_test.dart @@ -56,11 +56,17 @@ void main() { UIInteraction.setColor(Colors.black); await UIInteraction.selectTool(ToolData.CURSOR.name); + await UIInteraction.dragFromTo( + CanvasPosition.center, + CanvasPosition.topLeft, + ); + await UIInteraction.tapAt(CanvasPosition.center); await UIInteraction.dragFromTo( CanvasPosition.topLeft, CanvasPosition.bottomRight, + steps: 20, ); var color = await UIInteraction.getPixelColor( @@ -80,11 +86,18 @@ void main() { UIInteraction.setColor(Colors.black); await UIInteraction.selectTool(ToolData.CURSOR.name); + await UIInteraction.dragFromTo( + CanvasPosition.center, + CanvasPosition.topLeft, + steps: 20, + ); + await UIInteraction.tapAt(CanvasPosition.center); await UIInteraction.dragFromTo( CanvasPosition.topLeft, CanvasPosition.bottomRight, + steps: 20, ); var color = await UIInteraction.getPixelColor( @@ -104,11 +117,18 @@ void main() { UIInteraction.setColor(Colors.black); await UIInteraction.selectTool(ToolData.CURSOR.name); + await UIInteraction.dragFromTo( + CanvasPosition.center, + CanvasPosition.topLeft, + steps: 20, + ); + await UIInteraction.tapAt(CanvasPosition.center); await UIInteraction.dragFromTo( CanvasPosition.topLeft, - CanvasPosition.centerLeft, + CanvasPosition.center, + steps: 20, ); var color = await UIInteraction.getPixelColor( @@ -122,6 +142,7 @@ void main() { await UIInteraction.dragFromTo( CanvasPosition.centerRight, CanvasPosition.bottomRight, + steps: 20, ); color = await UIInteraction.getPixelColor( @@ -146,11 +167,13 @@ void main() { await UIInteraction.dragFromTo( CanvasPosition.center, CanvasPosition.topLeft, + steps: 20, ); await UIInteraction.dragFromTo( CanvasPosition.topLeft, CanvasPosition.bottomRight, + steps: 20, ); var color = await UIInteraction.getPixelColor( @@ -170,11 +193,18 @@ void main() { UIInteraction.setColor(Colors.black); await UIInteraction.selectTool(ToolData.CURSOR.name); + await UIInteraction.dragFromTo( + CanvasPosition.center, + CanvasPosition.topLeft, + steps: 20, + ); + await UIInteraction.tapAt(CanvasPosition.center); await UIInteraction.dragFromTo( CanvasPosition.topLeft, CanvasPosition.bottomRight, + steps: 20, ); var color = await UIInteraction.getPixelColor( @@ -254,18 +284,26 @@ void main() { UIInteraction.setColor(Colors.black); + await UIInteraction.dragFromTo( + CanvasPosition.center, + CanvasPosition.topLeft, + steps: 20, + ); + await UIInteraction.tapAt(CanvasPosition.center); await UIInteraction.dragFromTo( CanvasPosition.topLeft, CanvasPosition.bottomRight, + steps: 20, ); await UIInteraction.tapAt(CanvasPosition.center); await UIInteraction.dragFromTo( CanvasPosition.bottomRight, - CanvasPosition.topLeft, + CanvasPosition.center, + steps: 20, ); var color = await UIInteraction.getPixelColor( @@ -276,11 +314,18 @@ void main() { UIInteraction.setColor(Colors.red); + await UIInteraction.dragFromTo( + CanvasPosition.center, + CanvasPosition.topRight, + steps: 20, + ); + await UIInteraction.tapAt(CanvasPosition.center); await UIInteraction.dragFromTo( CanvasPosition.topRight, CanvasPosition.bottomLeft, + steps: 20, ); color = await UIInteraction.getPixelColor(