Skip to content

Commit

Permalink
Merge pull request Catrobat#34 from bakicelebi/PAINTROID-658
Browse files Browse the repository at this point in the history
PAINTROID-658 : Add hand tool
  • Loading branch information
juliajulie95 authored Dec 28, 2023
2 parents 0263227 + d5efbbc commit 8063d84
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 12 deletions.
47 changes: 47 additions & 0 deletions lib/tool/src/hand_tool/hand_tool.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'dart:ui';

import 'package:equatable/equatable.dart';
import 'package:paintroid/command/src/command_factory.dart';
import 'package:paintroid/command/src/command_manager.dart';
import 'package:paintroid/core/graphic_factory.dart';
import 'package:paintroid/tool/src/tool.dart';
import 'package:paintroid/tool/src/tool_types.dart';

class HandTool extends Tool with EquatableMixin {
HandTool({
required super.paint,
required super.commandFactory,
required super.commandManager,
required super.type,
});

@override
void onDown(Offset point) {}

@override
void onDrag(Offset point) {}

@override
void onUp(Offset? point) {}

@override
void onCancel() {}

@override
List<Object?> get props => [commandManager, commandFactory];

HandTool copyWith({
Paint? paint,
CommandFactory? commandFactory,
CommandManager? commandManager,
GraphicFactory? graphicFactory,
ToolType? type,
}) {
return HandTool(
paint: paint ?? this.paint,
commandFactory: commandFactory ?? this.commandFactory,
commandManager: commandManager ?? this.commandManager,
type: type ?? this.type,
);
}
}
18 changes: 18 additions & 0 deletions lib/tool/src/hand_tool/hand_tool_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:paintroid/command/src/command_factory_provider.dart';
import 'package:paintroid/command/src/command_manager_provider.dart';
import 'package:paintroid/tool/src/brush_tool/brush_tool_state_provider.dart';
import 'package:paintroid/tool/src/hand_tool/hand_tool.dart';
import 'package:paintroid/tool/src/tool_types.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'hand_tool_provider.g.dart';

@riverpod
HandTool handTool(HandToolRef ref) {
return HandTool(
paint: ref.watch(brushToolStateProvider.select((state) => state.paint)),
type: ToolType.HAND,
commandManager: ref.watch(commandManagerProvider),
commandFactory: ref.watch(commandFactoryProvider),
);
}
7 changes: 7 additions & 0 deletions lib/tool/src/toolbox/toolbox_state_provider.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:ui';

import 'package:paintroid/tool/src/hand_tool/hand_tool_provider.dart';
import 'package:paintroid/tool/tool.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:toast/toast.dart';
Expand Down Expand Up @@ -38,6 +39,12 @@ class ToolBoxState extends _$ToolBoxState {
currentToolType: ToolType.BRUSH,
);

break;
case ToolType.HAND:
state = state.copyWith(
currentTool: ref.read(handToolProvider),
currentToolType: ToolType.HAND,
);
break;
case ToolType.ERASER:
ref
Expand Down
14 changes: 5 additions & 9 deletions lib/workspace/src/ui/drawing_canvas.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:paintroid/service/device_service.dart';
import 'package:paintroid/tool/src/tool_types.dart';
import 'package:paintroid/tool/src/toolbox/toolbox_state_provider.dart';
import 'package:paintroid/workspace/src/state/canvas/canvas_state_provider.dart';
import 'package:paintroid/workspace/src/state/canvas_dirty_state.dart';
Expand Down Expand Up @@ -114,23 +115,18 @@ class _DrawingCanvasState extends ConsumerState<DrawingCanvas> {
_resetCanvasScale(fitToScreen: isFullscreen);
},
);
final canvasSize = ref.watch(
canvasStateProvider.select((state) => state.size),
);
final panningMargin = (canvasSize - const Offset(5, 5)) as Size;
return Listener(
onPointerDown: _onPointerDown,
onPointerUp: _onPointerUp,
child: InteractiveViewer(
clipBehavior: Clip.none,
transformationController: _transformationController,
boundaryMargin: EdgeInsets.symmetric(
horizontal: panningMargin.width,
vertical: panningMargin.height,
),
minScale: 0.2,
maxScale: 100,
panEnabled: false,
boundaryMargin: const EdgeInsets.all(double.infinity),
interactionEndFrictionCoefficient: double.minPositive,
panEnabled:
ref.watch(toolBoxStateProvider).currentTool.type == ToolType.HAND,
onInteractionStart: _onInteractionStart,
onInteractionUpdate: _onInteractionUpdate,
onInteractionEnd: _onInteractionEnd,
Expand Down
72 changes: 72 additions & 0 deletions test/unit/tool/hand_tool_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'dart:ui';

import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:paintroid/command/src/command_factory.dart';
import 'package:paintroid/command/src/command_manager.dart';
import 'package:paintroid/tool/src/hand_tool/hand_tool.dart';
import 'package:paintroid/tool/src/tool_types.dart';

import 'hand_tool_test.mocks.dart';

@GenerateMocks([
Paint,
CommandManager,
CommandFactory,
])
void main() {
late MockPaint mockPaint;
late MockCommandFactory mockCommandFactory;
late MockCommandManager mockCommandManager;

late HandTool sut;
const Offset offset = Offset(10, 10);

setUp(() {
mockPaint = MockPaint();
mockCommandFactory = MockCommandFactory();
mockCommandManager = MockCommandManager();

sut = HandTool(
paint: mockPaint,
commandFactory: mockCommandFactory,
commandManager: mockCommandManager,
type: ToolType.HAND,
);
});

group('HandTool Tests', () {
test('onDown should not interact with any dependencies', () {
sut.onDown(offset);

verifyNoMoreInteractions(mockPaint);
verifyNoMoreInteractions(mockCommandFactory);
verifyNoMoreInteractions(mockCommandManager);
});

test('onDrag should not interact with any dependencies', () {
sut.onDrag(offset);

verifyNoMoreInteractions(mockPaint);
verifyNoMoreInteractions(mockCommandFactory);
verifyNoMoreInteractions(mockCommandManager);
});

test('onUp should not interact with any dependencies', () {
sut.onUp(offset);

verifyNoMoreInteractions(mockPaint);
verifyNoMoreInteractions(mockCommandFactory);
verifyNoMoreInteractions(mockCommandManager);
});

test('onCancel should not interact with any dependencies', () {
sut.onCancel();

verifyNoMoreInteractions(mockPaint);
verifyNoMoreInteractions(mockCommandFactory);
verifyNoMoreInteractions(mockCommandManager);
});
});
}
4 changes: 2 additions & 2 deletions test/widget/tool/eraser_tool_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ void main() {
const testOffset = Offset(10, 10);

final pixelColorBeforeErase = await canvasInteractions
.drawLineFromCenter(testOffset)
.dragFromCenter(testOffset)
.then((_) => canvasInteractions.getPixelColor(300, 300));
expect(pixelColorBeforeErase, isNot(equals(0)));

await bottomNavBarInteractions.selectTool(ToolData.ERASER);

final pixelColorAfterErase = await canvasInteractions
.drawLineFromCenter(testOffset)
.dragFromCenter(testOffset)
.then((_) => canvasInteractions.getPixelColor(300, 300));
expect(pixelColorAfterErase, equals(0));
},
Expand Down
68 changes: 68 additions & 0 deletions test/widget/tool/hand_tool_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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/service/device_service.dart';
import 'package:paintroid/tool/tool.dart';
import 'package:paintroid/ui/pocket_paint.dart';

import '../utils/utils.dart';
import '../utils/interactions/interactive_viewer_interactions.dart';

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

late Widget sut;

setUp(() {
sut = ProviderScope(
overrides: [
IDeviceService.sizeProvider
.overrideWith((ref) => Future.value(const Size(600, 600)))
],
child: const MaterialApp(
home: PocketPaint(),
),
);
});

testWidgets('Pan bottom left', (WidgetTester tester) async {
await tester.pumpWidget(sut);
await tester.pumpAndSettle();

final bottomNavBarInteractions = BottomNavBarInteractions(tester);
final interactiveViewerInteractions = InterActiveViewerInteractions(tester);
await bottomNavBarInteractions.selectTool(ToolData.HAND);
await interactiveViewerInteractions.panAndVerify(const Offset(-50, 50));
});

testWidgets('Pan bottom right', (WidgetTester tester) async {
await tester.pumpWidget(sut);
await tester.pumpAndSettle();

final bottomNavBarInteractions = BottomNavBarInteractions(tester);
final interactiveViewerInteractions = InterActiveViewerInteractions(tester);
await bottomNavBarInteractions.selectTool(ToolData.HAND);
await interactiveViewerInteractions.panAndVerify(const Offset(50, 50));
});

testWidgets('Pan top left', (WidgetTester tester) async {
await tester.pumpWidget(sut);
await tester.pumpAndSettle();

final bottomNavBarInteractions = BottomNavBarInteractions(tester);
final interactiveViewerInteractions = InterActiveViewerInteractions(tester);
await bottomNavBarInteractions.selectTool(ToolData.HAND);
await interactiveViewerInteractions.panAndVerify(const Offset(-50, -50));
});

testWidgets('Pan top right', (WidgetTester tester) async {
await tester.pumpWidget(sut);
await tester.pumpAndSettle();

final bottomNavBarInteractions = BottomNavBarInteractions(tester);
final interactiveViewerInteractions = InterActiveViewerInteractions(tester);
await bottomNavBarInteractions.selectTool(ToolData.HAND);
await interactiveViewerInteractions.panAndVerify(const Offset(50, -50));
});
}
2 changes: 1 addition & 1 deletion test/widget/utils/interactions/canvas_interactions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class CanvasInteractions {

List<Offset> lastOffsets = [];

Future<CanvasInteractions> drawLineFromCenter(Offset offset) async {
Future<CanvasInteractions> dragFromCenter(Offset offset) async {
final drawingCanvasFinder = find.byType(CanvasPainter);
expect(drawingCanvasFinder, findsOneWidget);
await _tester.drag(drawingCanvasFinder, offset);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

class InterActiveViewerInteractions {
InterActiveViewerInteractions(this._tester);

double epsilon = 0.1;

final WidgetTester _tester;

Future<InterActiveViewerInteractions> panAndVerify(Offset offset) async {
final finder = find.byType(InteractiveViewer);

expect(finder, findsOneWidget);

InteractiveViewer interactiveViewer = _tester.widget(finder);

TransformationController controller =
interactiveViewer.transformationController!;

expect(controller, isNotNull);

final initialMatrix = controller.value;

await _tester.drag(finder, const Offset(-50, 50));
await _tester.pumpAndSettle();

double expectedX = initialMatrix.getTranslation().x - 50;
double expectedY = initialMatrix.getTranslation().y + 50;

expect(controller.value.getTranslation().x, closeTo(expectedX, epsilon));
expect(controller.value.getTranslation().y, closeTo(expectedY, epsilon));
return this;
}
}

0 comments on commit 8063d84

Please sign in to comment.