Created
August 12, 2024 04:32
-
-
Save lukas-h/93ba6d3a858927872583217c6f0a5322 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
class EditorScreen<T> extends StatefulWidget { | |
final double height; | |
final double width; | |
final Widget Function(BuildContext context, T? data) builder; | |
const EditorScreen({ | |
Key? key, | |
this.height = 200, | |
this.width = 200, | |
required this.builder, | |
}) : super(key: key); | |
@override | |
State<EditorScreen> createState() => _EditorScreenState(); | |
} | |
class _EditorScreenState<T> extends State<EditorScreen> { | |
List<Square<T>> squares = []; | |
int selectedSquareIndex = -1; | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text('Graphics Editor'), | |
), | |
body: GestureDetector( | |
onPanStart: (DragStartDetails details) { | |
setState(() { | |
Offset position = details.localPosition; | |
final square = Square<T>( | |
id: squares.length, | |
rect: Rect.fromLTWH( | |
position.dx, | |
position.dy, | |
widget.width, | |
widget.height, | |
), | |
isSelected: false, | |
data: null, | |
); | |
squares.add(square); | |
selectedSquareIndex = squares.length - 1; | |
}); | |
}, | |
onPanUpdate: (DragUpdateDetails details) { | |
setState(() { | |
if (selectedSquareIndex != -1) { | |
final square = squares[selectedSquareIndex]; | |
final x = details.globalPosition.dx - square.rect.left; | |
final y = details.globalPosition.dy - square.rect.top; | |
final newSize = Size( | |
x < 200 ? 200 : x, | |
y < 200 ? 200 : y, | |
); | |
square.rect = Rect.fromLTWH( | |
square.rect.left, | |
square.rect.top, | |
newSize.width, | |
newSize.height, | |
); | |
} | |
}); | |
}, | |
child: Stack( | |
children: [ | |
CustomPaint( | |
painter: ImagePainter(squares, selectedSquareIndex), | |
child: Container( | |
// Replace this container with your own image upload widget | |
color: Colors.grey[200], | |
), | |
), | |
for (var i = 0; i < squares.length; i++) | |
DraggableSquare<T>( | |
square: squares[i], | |
index: i, | |
isSelected: i == selectedSquareIndex, | |
builder: widget.builder, | |
onDragUpdate: (Offset offset) { | |
setState(() { | |
final square = squares[i]; | |
final x = (offset.dx - square.rect.left); | |
final y = offset.dy - square.rect.top; | |
final newSize = Size( | |
x < 200 ? 200 : x, | |
y < 200 ? 200 : y, | |
); | |
square.rect = Rect.fromLTWH( | |
square.rect.left, | |
square.rect.top, | |
newSize.width, | |
newSize.height, | |
); | |
selectedSquareIndex = i; | |
}); | |
}, | |
onTap: () { | |
setState(() { | |
selectedSquareIndex = i; | |
}); | |
}, | |
onPanEnd: (DragEndDetails details) {}, | |
), | |
], | |
), | |
), | |
); | |
} | |
} | |
class Square<T> { | |
final int id; | |
Rect rect; | |
bool isSelected; | |
T? data; | |
Square({ | |
required this.id, | |
required this.rect, | |
this.isSelected = false, | |
required this.data, | |
}); | |
} | |
class ImagePainter extends CustomPainter { | |
final List<Square> squares; | |
final int selectedSquareIndex; | |
ImagePainter(this.squares, this.selectedSquareIndex); | |
@override | |
void paint(Canvas canvas, Size size) { | |
Paint squarePaint = Paint() | |
..color = Colors.red | |
..style = PaintingStyle.stroke | |
..strokeWidth = 2.0; | |
for (var i = 0; i < squares.length; i++) { | |
final square = squares[i]; | |
if (square.isSelected || i == selectedSquareIndex) { | |
squarePaint.color = const Color(0xffd5bfff); | |
} else { | |
squarePaint.color = Colors.red; | |
} | |
canvas.drawRect(square.rect, squarePaint); | |
} | |
} | |
@override | |
bool shouldRepaint(covariant CustomPainter oldDelegate) { | |
return true; | |
} | |
} | |
class DraggableSquare<T> extends StatelessWidget { | |
final Square square; | |
final int index; | |
final bool isSelected; | |
final ValueChanged<Offset> onDragUpdate; | |
final VoidCallback onTap; | |
final ValueChanged<DragEndDetails> onPanEnd; | |
final Widget Function(BuildContext context, T? data) builder; | |
const DraggableSquare({ | |
super.key, | |
required this.square, | |
required this.index, | |
required this.isSelected, | |
required this.onDragUpdate, | |
required this.onTap, | |
required this.builder, | |
required this.onPanEnd, | |
}); | |
@override | |
Widget build(BuildContext context) { | |
return Positioned.fromRect( | |
rect: square.rect, | |
child: GestureDetector( | |
onPanUpdate: (details) { | |
onDragUpdate(details.globalPosition); | |
}, | |
onTap: onTap, | |
onPanEnd: onPanEnd, | |
child: Container( | |
decoration: BoxDecoration( | |
color: isSelected | |
? Theme.of(context).primaryColor.withOpacity(0.5) | |
: Colors.transparent, | |
border: Border.all( | |
color: isSelected ? Theme.of(context).primaryColor : Colors.red, | |
width: 2.0, | |
), | |
), | |
child: builder(context, square.data), | |
), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment