Open jtkeyva opened 2 years ago
This should be possible easily by iterating through the Lines and points. Currently it's outside of the scope of our team, but feel free to open a PR!
Ok thanks. Do you have any quick bits of advice on where to start?
@timcreatedit i've been trying this for hours. can you give me a couple clues on how to do this? i'd like to store the json for replay at a later time but i'm stumped. any help appreciated.
here's some code i modified but it's broken: `import 'dart:convert'; 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
class _HomePageState extends State
// Initialize the list of strokes. List<Map<String, dynamic>> savedStrokes = [];
@override void initState() { notifier = ScribbleNotifier(); notifier.addListener((ScribbleState state) { // Update the saved strokes list whenever the notifier state changes. savedStrokes = notifier._strokes.map<Map<String, dynamic>>( (stroke) { return { 'path': stroke.path .computeMetrics() .map((metric) => metric.extractPath(0, metric.length)) .map((path) => path.toSvgString()) .toList(), 'color': stroke.color.value, 'strokeWidth': stroke.strokeWidth, }; }, ).toList(); }); super.initState(); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), leading: IconButton( icon: const Icon(Icons.save), tooltip: "Save to Image", onPressed: () => _saveImage(context), ), ), body: SingleChildScrollView( child: SizedBox( height: MediaQuery.of(context).size.height * 2, child: Stack( children: [ Scribble( notifier: notifier, drawPen: true, ), Positioned( top: 16, right: 16, child: Column( children: [ _buildReplayButton(context), const Divider( height: 32, ), _buildColorToolbar(context), const Divider( height: 32, ), _buildStrokeToolbar(context), ], ), ) ], ), ), ), ); }
// Add a replay button to the toolbar. Widget _buildReplayButton(BuildContext context) { return FloatingActionButton.small( tooltip: "Replay", onPressed: () => _replayDrawing(context), backgroundColor: Colors.blueGrey, child: const Icon(Icons.play_arrow), ); }
Future
// The replay function.
Future
// Iterate through the saved strokes and redraw them on a canvas.
for (var stroke in savedStrokes) {
final path = Path();
for (var offset in stroke['path']) {
path.lineTo(offset['dx'], offset['dy']);
}
final color = Color(stroke['color']);
final strokeWidth = stroke['strokeWidth'];
replayNotifier.draw(
path: path,
color: color,
strokeWidth: strokeWidth,
);
}
// Display the replayed drawing in a dialog.
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("Replayed Drawing"),
content: StateNotifierBuilder<ScribbleState>(
stateNotifier: replayNotifier,
builder: (context, state, _) => SizedBox(
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.height * 0.8,
child: Scribble(
notifier: replayNotifier,
drawPen: false,
),
),
),
),
);
}
Widget buildColorToolbar(BuildContext context) {
return StateNotifierBuilder
Widget _buildPointerModeSwitcher(BuildContext context,
{required bool penMode}) {
return FloatingActionButton.small(
onPressed: () => notifier.setAllowedPointersMode(
penMode ? ScribblePointerMode.all : ScribblePointerMode.penOnly,
),
tooltip:
"Switch drawing mode to " + (penMode ? "all pointers" : "pen only"),
child: AnimatedSwitcher(
duration: kThemeAnimationDuration,
child: !penMode
? const Icon(
Icons.touch_app,
key: ValueKey(true),
)
: const Icon(
Icons.do_not_touch,
key: ValueKey(false),
),
),
);
}
Widget buildStrokeToolbar(BuildContext context) {
return StateNotifierBuilder
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) => Color(s.selectedColor), erasing: () => Colors.transparent, ), border: state.map( drawing: () => null, erasing: () => Border.all(width: 1), ), borderRadius: BorderRadius.circular(50.0)), ), ), ), ); }
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.value; 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), ); } } `
@jtkeyva
Did you get it working? If not what is issue?
@danschewy i ended up making one from scratch using chat gpt. it works ok but not line width and it's a bit hacky
Is it possible to "play" the scribble from JSON? Like as if it is being drawn from scratch? Thanks