Skip to content

Create cursor_tool object and provider. #117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,22 @@ 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)
- SDWebImage/Core (5.19.2)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite (0.0.3):
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- SwiftyGif (5.4.5)
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -109,8 +109,8 @@ 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"

Expand All @@ -121,15 +121,15 @@ SPEC CHECKSUMS:
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_localization: f43b18844a2b3d2c71fd64f04ffd6b1e64dd54d4
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
launch_review: 75d5a956ba8eaa493e9c9d4bf4c05e505e8d5ed0
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
launch_review_latest: d405bc299b841153fc24f566d145b67a49c5245b
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
SDWebImage: dfe95b2466a9823cf9f0c6d01217c06550d7b29a
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe

Expand Down
18 changes: 18 additions & 0 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
C143E155B1BF9D937E877BC4 /* [CP] Embed Pods Frameworks */,
CE347BAC542BC77A1AF7E630 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
Expand Down Expand Up @@ -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 */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
Expand Down
7 changes: 7 additions & 0 deletions lib/core/commands/command_painter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:paintroid/core/commands/command_manager/command_manager_provider
import 'package:paintroid/core/enums/tool_types.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';
Expand All @@ -23,6 +24,7 @@ class CommandPainter extends CustomPainter {
if (currentTool.type != ToolType.SHAPES) {
canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height));
}

switch (currentTool.type) {
case ToolType.LINE:
_drawGhostPathsAndVertices(canvas, currentTool as LineTool);
Expand All @@ -32,6 +34,11 @@ class CommandPainter extends CustomPainter {
..drawShape(canvas, ref.read(paintProvider))
..drawGuides(canvas);
break;
case ToolType.CURSOR:
commandManager.executeLastCommand(canvas);
(currentTool as CursorTool)
.drawCursorIcon(canvas, ref.read(paintProvider));
break;
default:
commandManager.executeLastCommand(canvas);
break;
Expand Down
27 changes: 27 additions & 0 deletions lib/core/providers/object/tools/cursor_tool_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'dart:ui';

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';
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() {
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,
);
}
}
27 changes: 27 additions & 0 deletions lib/core/providers/object/tools/cursor_tool_provider.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions lib/core/providers/state/toolbox_state_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -77,6 +78,9 @@ 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));
Copy link
Preview

Copilot AI Jul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When selecting the cursor tool, the paint stroke width isn’t updated. You may need to call ref.read(paintProvider.notifier).updateStrokeWidth(...) with the cursor’s stroke width to ensure drawing uses the correct thickness.

Suggested change
state = state.copyWith(currentTool: ref.read(cursorToolProvider));
state = state.copyWith(currentTool: ref.read(cursorToolProvider));
ref.read(paintProvider.notifier).updateStrokeWidth(1.0); // Default stroke width for cursor

Copilot uses AI. Check for mistakes.

break;
default:
state = state.copyWith(currentTool: ref.read(brushToolProvider));
break;
Expand Down
2 changes: 1 addition & 1 deletion lib/core/providers/state/toolbox_state_provider.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

161 changes: 161 additions & 0 deletions lib/core/tools/implementation/cursor_tool.dart
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading