Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Playback Scribble #26

Open
jtkeyva opened this issue Oct 18, 2022 · 5 comments
Open

Playback Scribble #26

jtkeyva opened this issue Oct 18, 2022 · 5 comments
Labels
enhancement New feature or request good first issue Good for newcomers

Comments

@jtkeyva
Copy link

jtkeyva commented Oct 18, 2022

Is it possible to "play" the scribble from JSON? Like as if it is being drawn from scratch?
Thanks

@timcreatedit
Copy link
Owner

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!

@timcreatedit timcreatedit added enhancement New feature or request good first issue Good for newcomers labels Nov 2, 2022
@jtkeyva
Copy link
Author

jtkeyva commented Nov 4, 2022

Ok thanks. Do you have any quick bits of advice on where to start?

@jtkeyva
Copy link
Author

jtkeyva commented May 6, 2023

@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 createState() => _HomePageState();
}

class _HomePageState extends State {
late ScribbleNotifier notifier;

// 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 _saveImage(BuildContext context) async {
final image = await notifier.renderImage();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("Your Image"),
content: Image.memory(image.buffer.asUint8List()),
),
);
}

// The replay function.
Future _replayDrawing(BuildContext context) async {
// Create a new ScribbleNotifier for the replay.
final replayNotifier = ScribbleNotifier();

// 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(
stateNotifier: notifier,
builder: (context, state, _) => 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,
),
_buildPointerModeSwitcher(context,
penMode:
state.allowedPointersMode == ScribblePointerMode.penOnly),
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 _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(
stateNotifier: notifier,
builder: (context, state, _) => 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) => 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),
);
}
}
`

@danschewy
Copy link

danschewy commented Aug 5, 2023

@jtkeyva

Did you get it working? If not what is issue?

@jtkeyva
Copy link
Author

jtkeyva commented Aug 17, 2023

@danschewy i ended up making one from scratch using chat gpt. it works ok but not line width and it's a bit hacky

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

3 participants