Skip to content

Commit

Permalink
PAINTROID-768: Add Spray Can tool (#98)
Browse files Browse the repository at this point in the history
* test

* test

* PAINTROID-768: Flutter: Add Spray Can tool

* PAINTROID-768: Flutter: Add Spray Can tool

* PAINTROID-768: Flutter: Add Spray Can tool - tests

* PAINTROID-768: Flutter: Add Spray Can tool - CR

* PAINTROID-768: Flutter: Add Spray Can tool - CR

* PAINTROID-768: Flutter: Add Spray Can tool - CR

* PAINTROID-768: Flutter: Add Spray Can tool - CR
  • Loading branch information
Lenkomotive authored Jan 6, 2025
1 parent 269d3c8 commit 3a48447
Show file tree
Hide file tree
Showing 28 changed files with 810 additions and 88 deletions.
12 changes: 6 additions & 6 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,18 @@ SPEC CHECKSUMS:
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_localization: f43b18844a2b3d2c71fd64f04ffd6b1e64dd54d4
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
launch_review: 75d5a956ba8eaa493e9c9d4bf4c05e505e8d5ed0
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
SDWebImage: dfe95b2466a9823cf9f0c6d01217c06550d7b29a
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812

PODFILE CHECKSUM: 303789365c3a8d7bc562e5e65d7e8e15218ec5c6

COCOAPODS: 1.15.2
COCOAPODS: 1.15.0
5 changes: 5 additions & 0 deletions lib/core/commands/command_factory/command_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:paintroid/core/commands/command_implementation/graphic/line_comm
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/commands/command_implementation/graphic/spray_command.dart';
import 'package:paintroid/core/commands/path_with_action_history.dart';

class CommandFactory {
Expand Down Expand Up @@ -38,4 +39,8 @@ class CommandFactory {
Offset center,
) =>
CircleShapeCommand(paint, radius, center);

SprayCommand createSprayCommand(List<Offset> points, Paint paint) {
return SprayCommand(points, paint);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'dart:ui';

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart';
import 'package:paintroid/core/json_serialization/converter/offset_converter.dart';
import 'package:paintroid/core/json_serialization/converter/paint_converter.dart';
import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart';
import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart';

part 'spray_command.g.dart';

@JsonSerializable()
class SprayCommand extends GraphicCommand {
final String type;
final int version;

@OffsetConverter()
List<Offset> points;

SprayCommand(
this.points,
super.paint, {
this.type = SerializerType.SPRAY_COMMAND,
int? version,
}) : version =
version ?? VersionStrategyManager.strategy.getSprayCommandVersion();

@override
void call(Canvas canvas) {
canvas.drawPoints(PointMode.points, points, paint);
}

@override
List<Object?> get props => [paint, points];

@override
Map<String, dynamic> toJson() => _$SprayCommandToJson(this);

factory SprayCommand.fromJson(Map<String, dynamic> json) {
int version = json['version'] as int;

switch (version) {
case Version.v1:
return _$SprayCommandFromJson(json);
default:
return _$SprayCommandFromJson(json);
}
}
}

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/commands/command_manager/command_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:paintroid/core/commands/command_implementation/graphic/shape/squ
import 'package:paintroid/core/tools/line_tool/vertex.dart';
import 'package:paintroid/core/tools/line_tool/vertex_stack.dart';
import 'package:paintroid/core/tools/tool_data.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/spray_command.dart';

enum ActionType { UNDO, REDO }

Expand Down Expand Up @@ -109,6 +110,9 @@ class CommandManager {
return ToolData.SHAPES;
} else if (command.runtimeType == CircleShapeCommand) {
return ToolData.SHAPES;
}
else if (command.runtimeType == SprayCommand) {
return ToolData.SPRAY;
} else {
return ToolData.BRUSH;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class SerializerVersion {
static const int LINE_COMMAND_VERSION = Version.v1;
static const int SQUARE_SHAPE_COMMAND_VERSION = Version.v1;
static const int CIRCLE_SHAPE_COMMAND_VERSION = Version.v1;
static const int SPRAY_COMMAND_VERSION = Version.v1;
}

class Version {
Expand All @@ -21,4 +22,5 @@ class SerializerType {
static const String CLOSE_ACTION = 'CloseAction';
static const String SQUARE_SHAPE_COMMAND = 'SquareShapeCommand';
static const String CIRCLE_SHAPE_COMMAND = 'CircleShapeCommand';
static const String SPRAY_COMMAND = 'SprayCommand';
}
9 changes: 9 additions & 0 deletions lib/core/json_serialization/versioning/version_strategy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ import 'package:paintroid/core/json_serialization/versioning/serializer_version.

abstract class IVersionStrategy {
int getCatrobatImageVersion();

int getPathCommandVersion();

int getLineCommandVersion();

int getSquareShapeCommandVersion();

int getCircleShapeCommandVersion();

int getSprayCommandVersion();
}

class ProductionVersionStrategy implements IVersionStrategy {
Expand All @@ -25,6 +31,9 @@ class ProductionVersionStrategy implements IVersionStrategy {
@override
int getCircleShapeCommandVersion() =>
SerializerVersion.CIRCLE_SHAPE_COMMAND_VERSION;

@override
int getSprayCommandVersion() => SerializerVersion.SPRAY_COMMAND_VERSION;
}

class VersionStrategyManager {
Expand Down
3 changes: 1 addition & 2 deletions lib/core/providers/state/paint_provider.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import 'dart:ui';

import 'package:riverpod_annotation/riverpod_annotation.dart';

import 'package:paintroid/core/commands/graphic_factory/graphic_factory.dart';
import 'package:paintroid/core/commands/graphic_factory/graphic_factory_provider.dart';
import 'package:paintroid/core/enums/tool_types.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'paint_provider.g.dart';

Expand Down
24 changes: 24 additions & 0 deletions lib/core/providers/state/spray_tool_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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';
import 'package:paintroid/core/providers/state/canvas_state_provider.dart';
import 'package:paintroid/core/tools/implementation/spray_tool.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'spray_tool_provider.g.dart';

@riverpod
class SprayToolProvider extends _$SprayToolProvider {
@override
SprayTool build() {
return SprayTool(
type: ToolType.SPRAY,
commandManager: ref.watch(commandManagerProvider),
commandFactory: ref.watch(commandFactoryProvider),
graphicFactory: ref.watch(graphicFactoryProvider),
drawingSurfaceSize:
ref.watch(canvasStateProvider.select((state) => state.size)),
);
}
}
26 changes: 26 additions & 0 deletions lib/core/providers/state/spray_tool_provider.g.dart

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

19 changes: 16 additions & 3 deletions lib/core/providers/state/toolbox_state_provider.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import 'dart:ui';
import 'package:paintroid/core/providers/object/canvas_painter_provider.dart';
import 'package:paintroid/core/providers/object/tools/shapes_tool_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.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/object/canvas_painter_provider.dart';
import 'package:paintroid/core/providers/object/tools/brush_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';
import 'package:paintroid/core/providers/object/tools/shapes_tool_provider.dart';
import 'package:paintroid/core/providers/state/paint_provider.dart';
import 'package:paintroid/core/providers/state/spray_tool_provider.dart';
import 'package:paintroid/core/providers/state/toolbox_state_data.dart';
import 'package:paintroid/core/tools/implementation/spray_tool.dart';
import 'package:paintroid/core/tools/tool_data.dart';
import 'package:paintroid/ui/utils/toast_utils.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'toolbox_state_provider.g.dart';

Expand Down Expand Up @@ -47,6 +50,10 @@ class ToolBoxStateProvider extends _$ToolBoxStateProvider {
}

void switchTool(ToolData data) {
if (state.currentTool is SprayTool) {
final currentRadius = (state.currentTool as SprayTool).sprayRadius;
ref.read(paintProvider.notifier).updateStrokeWidth(currentRadius);
}
switch (data.type) {
case ToolType.BRUSH:
state = state.copyWith(currentTool: ref.read(brushToolProvider));
Expand All @@ -64,6 +71,12 @@ class ToolBoxStateProvider extends _$ToolBoxStateProvider {
state = state.copyWith(currentTool: ref.read(shapesToolProvider));
ref.read(canvasPainterProvider.notifier).repaint();
break;
case ToolType.SPRAY:
state = state.copyWith(currentTool: ref.read(sprayToolProvider));
final currentStrokeWidth = ref.read(paintProvider).strokeWidth;
(state.currentTool as SprayTool).updateSprayRadius(currentStrokeWidth);
ref.read(paintProvider.notifier).updateStrokeWidth(SPRAY_TOOL_RADIUS);
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.

86 changes: 86 additions & 0 deletions lib/core/tools/implementation/spray_tool.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import 'dart:math';
import 'dart:ui';

import 'package:flutter/foundation.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/spray_command.dart';
import 'package:paintroid/core/commands/graphic_factory/graphic_factory.dart';
import 'package:paintroid/core/tools/tool.dart';

const SPRAY_TOOL_RADIUS = 10.0;

class SprayTool extends Tool {
final GraphicFactory graphicFactory;
final Size drawingSurfaceSize;

@visibleForTesting
late SprayCommand sprayCommand;
late Paint paint;

SprayTool({
required super.commandFactory,
required super.commandManager,
required this.graphicFactory,
required super.type,
required this.drawingSurfaceSize,
super.hasAddFunctionality = false,
super.hasFinalizeFunctionality = false,
});

double sprayRadius = 20;
final Random random = Random();

@override
void onDown(Offset point, Paint paint) {
this.paint = graphicFactory.copyPaint(paint);
final initialPoints = _generateSprayPoints(point);
sprayCommand = commandFactory.createSprayCommand(initialPoints, this.paint);
commandManager.addGraphicCommand(sprayCommand);
}

@override
void onDrag(Offset point, Paint paint) {
final newPoints = _generateSprayPoints(point);
sprayCommand.points.addAll(newPoints);
}

@override
void onUp(Offset point, Paint paint) {}

@override
void onCancel() {
commandManager.discardLastCommand();
}

@override
void onCheckmark(Paint paint) {}

@override
void onPlus() {}

@override
void onRedo() {
commandManager.redo();
}

@override
void onUndo() {
commandManager.undo();
}

void updateSprayRadius(double newRadius) {
sprayRadius = newRadius;
}

List<Offset> _generateSprayPoints(Offset center) {
List<Offset> points = [];
final density = sprayRadius / 3;
for (int i = 0; i < density; i++) {
final angle = random.nextDouble() * 2 * pi;
final radius = sqrt(random.nextDouble()) * sprayRadius * 2;
final dx = center.dx + radius * cos(angle);
final dy = center.dy + radius * sin(angle);
points.add(Offset(dx, dy));
}
return points;
}
}
Loading

0 comments on commit 3a48447

Please sign in to comment.