Created
January 18, 2023 13:40
-
-
Save l0rinc/eba33bd71b33d6b805627b53c57037cc to your computer and use it in GitHub Desktop.
This file contains hidden or 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:collection/collection.dart'; | |
import 'package:flutter/material.dart'; | |
void main() => runApp(const SticksApp()); | |
class SticksApp extends StatelessWidget { | |
const SticksApp({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return const MaterialApp( | |
debugShowCheckedModeBanner: false, | |
home: Scaffold(body: SticksWidget()), | |
); | |
} | |
} | |
class SticksWidget extends StatefulWidget { | |
const SticksWidget({super.key}); | |
@override | |
State<SticksWidget> createState() => SticksWidgetState(); | |
} | |
class SticksWidgetState extends State<SticksWidget> { | |
var data = '\u{0}\u{1}\u{2}\u{3}\u{4}\u{5}\u{6}\u{7}\u{8}\u{9} - secret message'; | |
@override | |
Widget build(BuildContext context) { | |
var width = MediaQuery.of(context).size.width; | |
return Padding( | |
padding: const EdgeInsets.all(10.0), | |
child: Column( | |
children: [ | |
TextFormField( | |
initialValue: data, | |
autovalidateMode: AutovalidateMode.always, | |
validator: (value) => isValidInput(value) ? null : "Only ASCII chars accepted!", | |
onChanged: (value) { | |
if (isValidInput(value)) { | |
setState(() { | |
data = value; | |
}); | |
} | |
}, | |
), | |
const SizedBox(height: 5), | |
CustomPaint( | |
size: Size(width, 100), | |
painter: SticksPainter(data), | |
), | |
], | |
), | |
); | |
} | |
bool isValidInput(String? value) => (value ?? '').codeUnits.every((c) => c >= 0 && c <= 255); | |
} | |
class SticksPainter extends CustomPainter { | |
final String data; | |
SticksPainter(this.data); | |
@override | |
void paint(Canvas canvas, Size size) { | |
var width = 15.0; | |
var height = 20.0; | |
var offset = const Offset(0.0, 0.0); | |
for (var c in data.codeUnits) { | |
drawChar(canvas, c, offset, width, height); | |
var textPainter = TextPainter( | |
text: TextSpan(text: c.toString().padLeft(3, ' '), style: const TextStyle(fontSize: 8, color: Colors.black)), | |
textDirection: TextDirection.ltr, | |
maxLines: 1, | |
textAlign: TextAlign.center, | |
)..layout(); | |
textPainter.paint(canvas, offset.translate(0, height + 3)); | |
offset = offset.translate(width * 1.3, 0); | |
if (offset.dx + width > size.width) { | |
offset = Offset(0, offset.dy + height * 2); | |
} | |
} | |
} | |
void drawChar(Canvas canvas, int value, Offset offset, double width, double height) { | |
var bounds = Rect.fromLTWH(offset.dx, offset.dy, width, height); | |
canvas.drawLine(bounds.topCenter, bounds.bottomCenter, getPaint()); | |
value.toRadixString(2).padLeft(8, '0').characters.map((v) => v == "1").forEachIndexed((i, shouldDraw) { | |
if (shouldDraw) { | |
Offset from, to; | |
/*@formatter:off*/ | |
switch (i) { | |
case 0: from = bounds.topLeft; to = bounds.topCenter; break; | |
case 1: from = bounds.topCenter; to = bounds.topRight; break; | |
case 2: from = bounds.topLeft; to = bounds.center; break; | |
case 3: from = bounds.topRight; to = bounds.center; break; | |
case 4: from = bounds.center; to = bounds.bottomLeft; break; | |
case 5: from = bounds.center; to = bounds.bottomRight; break; | |
case 6: from = bounds.bottomLeft; to = bounds.bottomCenter; break; | |
case 7: from = bounds.bottomCenter; to = bounds.bottomRight; break; | |
default: throw UnsupportedError(i.toString()); | |
} | |
/*@formatter:on*/ | |
canvas.drawLine(from, to, getPaint()); | |
} | |
}); | |
} | |
Paint getPaint() { | |
return Paint() | |
..color = Colors.black | |
..strokeWidth = 2 | |
..strokeCap = StrokeCap.round | |
..strokeJoin = StrokeJoin.round; | |
} | |
@override | |
bool shouldRepaint(_) => true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment