scribble 0.0.3 scribble: ^0.0.3 copied to clipboard
Scribble is a lightweight library for freehand drawing in Flutter supporting pressure, variable line width and more!
import 'package:flutter/material.dart';
import 'package:flutter_state_notifier/flutter_state_notifier.dart';
import 'package:scribble/scribble.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Scribble',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(title: 'Scribble'),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late ScribbleNotifier notifier;
@override
void initState() {
notifier = ScribbleNotifier();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Stack(
children: [
Scribble(
notifier: notifier,
drawPointer: true,
),
_buildColorToolbar(context),
_buildStrokeToolbar(context),
],
),
);
}
Widget _buildStrokeToolbar(BuildContext context) {
return StateNotifierBuilder<ScribbleState>(
stateNotifier: notifier,
builder: (context, state, _) => Positioned(
bottom: 16,
right: 16,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
for (final w in notifier.widths)
_buildStrokeButton(
context,
strokeWidth: w,
state: state,
),
],
),
),
);
}
Widget _buildStrokeButton(
BuildContext context, {
required double strokeWidth,
required ScribbleState state,
}) {
final selected = state.selectedWidth == strokeWidth;
return Padding(
padding: const EdgeInsets.all(4),
child: Material(
elevation: selected ? 4 : 0,
shape: const CircleBorder(),
child: InkWell(
onTap: () => notifier.setStrokeWidth(strokeWidth),
customBorder: const CircleBorder(),
child: AnimatedContainer(
duration: kThemeAnimationDuration,
width: strokeWidth * 2,
height: strokeWidth * 2,
decoration: BoxDecoration(
color: state.map(
drawing: (s) => s.selectedColor,
erasing: (_) => Colors.transparent,
),
border: state.map(
drawing: (_) => null,
erasing: (_) => Border.all(width: 1),
),
borderRadius: BorderRadius.circular(50.0)),
),
),
),
);
}
Widget _buildColorToolbar(BuildContext context) {
return StateNotifierBuilder<ScribbleState>(
stateNotifier: notifier,
builder: (context, state, _) => Positioned(
top: 16,
right: 16,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
_buildUndoButton(context),
const Divider(
height: 4.0,
),
_buildRedoButton(context),
const Divider(
height: 4.0,
),
_buildClearButton(context),
const Divider(
height: 20.0,
),
_buildEraserButton(context, isSelected: state is Erasing),
_buildColorButton(context, color: Colors.black, state: state),
_buildColorButton(context, color: Colors.red, state: state),
_buildColorButton(context, color: Colors.green, state: state),
_buildColorButton(context, color: Colors.blue, state: state),
_buildColorButton(context, color: Colors.yellow, state: state),
],
),
),
);
}
Widget _buildEraserButton(BuildContext context, {required bool isSelected}) {
return Padding(
padding: const EdgeInsets.all(4),
child: FloatingActionButton.small(
tooltip: "Erase",
backgroundColor: const Color(0xFFF7FBFF),
elevation: isSelected ? 10 : 2,
shape: !isSelected
? const CircleBorder()
: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: const Icon(Icons.remove, color: Colors.blueGrey),
onPressed: notifier.setEraser,
),
);
}
Widget _buildColorButton(
BuildContext context, {
required Color color,
required ScribbleState state,
}) {
final isSelected = state is Drawing && state.selectedColor == color;
return Padding(
padding: const EdgeInsets.all(4),
child: FloatingActionButton.small(
backgroundColor: color,
elevation: isSelected ? 10 : 2,
shape: !isSelected
? const CircleBorder()
: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: Container(),
onPressed: () => notifier.setColor(color)),
);
}
Widget _buildUndoButton(
BuildContext context,
) {
return FloatingActionButton.small(
tooltip: "Undo",
onPressed: notifier.canUndo ? notifier.undo : null,
disabledElevation: 0,
backgroundColor: notifier.canUndo ? Colors.blueGrey : Colors.grey,
child: const Icon(
Icons.undo_rounded,
color: Colors.white,
),
);
}
Widget _buildRedoButton(
BuildContext context,
) {
return FloatingActionButton.small(
tooltip: "Redo",
onPressed: notifier.canRedo ? notifier.redo : null,
disabledElevation: 0,
backgroundColor: notifier.canRedo ? Colors.blueGrey : Colors.grey,
child: const Icon(
Icons.redo_rounded,
color: Colors.white,
),
);
}
Widget _buildClearButton(BuildContext context) {
return FloatingActionButton.small(
tooltip: "Clear",
onPressed: notifier.clear,
disabledElevation: 0,
backgroundColor: Colors.blueGrey,
child: const Icon(Icons.clear),
);
}
}