Skip to content

Instantly share code, notes, and snippets.

@lukas-h
Created August 12, 2024 04:32
Show Gist options
  • Save lukas-h/93ba6d3a858927872583217c6f0a5322 to your computer and use it in GitHub Desktop.
Save lukas-h/93ba6d3a858927872583217c6f0a5322 to your computer and use it in GitHub Desktop.
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