-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from jonahwilliams/runtime
add basic vector graphics asset
- Loading branch information
Showing
5 changed files
with
402 additions
and
12 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,11 @@ | ||
# CHANGELOG | ||
|
||
## 0.0.1 | ||
* Added `VectorGraphic` which consumes encoded vector graphics assets using | ||
a `BytesLoader`. | ||
* Added `AssetBytesLoader` and `NetworkBytesLoader` as example loader | ||
implementations. | ||
|
||
## 0.0.0 | ||
|
||
* Create repository |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import 'dart:ui' as ui; | ||
import 'dart:typed_data'; | ||
|
||
import 'package:vector_graphics_codec/vector_graphics_codec.dart'; | ||
|
||
/// A listener implementation for the vector graphics codec that converts the | ||
/// format into a [ui.Picture]. | ||
class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener { | ||
/// Create a new [FlutterVectorGraphicsListener]. | ||
factory FlutterVectorGraphicsListener() { | ||
final ui.PictureRecorder recorder = ui.PictureRecorder(); | ||
final ui.Canvas canvas = ui.Canvas(recorder); | ||
return FlutterVectorGraphicsListener._(canvas, recorder); | ||
} | ||
|
||
FlutterVectorGraphicsListener._(this._canvas, this._recorder); | ||
|
||
final ui.PictureRecorder _recorder; | ||
final ui.Canvas _canvas; | ||
final List<ui.Paint> _paints = <ui.Paint>[]; | ||
final List<ui.Path> _paths = <ui.Path>[]; | ||
ui.Path? _currentPath; | ||
bool _done = false; | ||
|
||
static final _emptyPaint = ui.Paint(); | ||
|
||
/// Convert the vector graphics asset this listener decoded into a [ui.Picture]. | ||
/// | ||
/// This method can only be called once for a given listener instance. | ||
ui.Picture toPicture() { | ||
assert(!_done); | ||
_done = true; | ||
return _recorder.endRecording(); | ||
} | ||
|
||
@override | ||
void onDrawPath(int pathId, int? paintId) { | ||
final ui.Path path = _paths[pathId]; | ||
ui.Paint? paint; | ||
if (paintId != null) { | ||
paint = _paints[paintId]; | ||
} | ||
_canvas.drawPath(path, paint ?? _emptyPaint); | ||
} | ||
|
||
@override | ||
void onDrawVertices(Float32List vertices, Uint16List? indices, int? paintId) { | ||
final ui.Vertices vextexData = | ||
ui.Vertices.raw(ui.VertexMode.triangles, vertices, indices: indices); | ||
ui.Paint? paint; | ||
if (paintId != null) { | ||
paint = _paints[paintId]; | ||
} | ||
_canvas.drawVertices( | ||
vextexData, ui.BlendMode.srcOver, paint ?? _emptyPaint); | ||
} | ||
|
||
@override | ||
void onPaintObject({ | ||
required int color, | ||
required int? strokeCap, | ||
required int? strokeJoin, | ||
required int blendMode, | ||
required double? strokeMiterLimit, | ||
required double? strokeWidth, | ||
required int paintStyle, | ||
required int id, | ||
}) { | ||
assert(_paints.length == id, 'Expect ID to be ${_paints.length}'); | ||
final ui.Paint paint = ui.Paint()..color = ui.Color(color); | ||
|
||
if (blendMode != 0) { | ||
paint.blendMode = ui.BlendMode.values[blendMode]; | ||
} | ||
|
||
if (paintStyle == 1) { | ||
paint.style = ui.PaintingStyle.stroke; | ||
if (strokeCap != null && strokeCap != 0) { | ||
paint.strokeCap = ui.StrokeCap.values[strokeCap]; | ||
} | ||
if (strokeJoin != null && strokeJoin != 0) { | ||
paint.strokeJoin = ui.StrokeJoin.values[strokeJoin]; | ||
} | ||
if (strokeMiterLimit != null && strokeMiterLimit != 4.0) { | ||
paint.strokeMiterLimit = strokeMiterLimit; | ||
} | ||
if (strokeWidth != null && strokeWidth != 1.0) { | ||
paint.strokeWidth = strokeWidth; | ||
} | ||
} | ||
_paints.add(paint); | ||
} | ||
|
||
@override | ||
void onPathClose() { | ||
_currentPath!.close(); | ||
} | ||
|
||
@override | ||
void onPathCubicTo( | ||
double x1, double y1, double x2, double y2, double x3, double y3) { | ||
_currentPath!.cubicTo(x1, y1, x2, y2, x3, y3); | ||
} | ||
|
||
@override | ||
void onPathFinished() { | ||
_currentPath = null; | ||
} | ||
|
||
@override | ||
void onPathLineTo(double x, double y) { | ||
_currentPath!.lineTo(x, y); | ||
} | ||
|
||
@override | ||
void onPathMoveTo(double x, double y) { | ||
_currentPath!.moveTo(x, y); | ||
} | ||
|
||
@override | ||
void onPathStart(int id, int fillType) { | ||
assert(_currentPath == null); | ||
assert(_paths.length == id, 'Expected Id to be $id'); | ||
|
||
final ui.Path path = ui.Path(); | ||
path.fillType = ui.PathFillType.values[fillType]; | ||
_paths.add(path); | ||
_currentPath = path; | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,209 @@ | ||
library vector_graphics; | ||
import 'dart:typed_data'; | ||
import 'dart:ui' as ui; | ||
import 'dart:io'; | ||
|
||
/// A Calculator. | ||
class Calculator { | ||
/// Returns [value] plus 1. | ||
int addOne(int value) => value + 1; | ||
import 'package:flutter/foundation.dart'; | ||
import 'package:flutter/rendering.dart'; | ||
import 'package:flutter/widgets.dart'; | ||
|
||
import 'package:vector_graphics_codec/vector_graphics_codec.dart'; | ||
|
||
import 'src/listener.dart'; | ||
|
||
const VectorGraphicsCodec _codec = VectorGraphicsCodec(); | ||
|
||
/// Decode a vector graphics binary asset into a [ui.Picture]. | ||
/// | ||
/// Throws a [StateError] if the data is invalid. | ||
ui.Picture decodeVectorGraphics(ByteData data) { | ||
final FlutterVectorGraphicsListener listener = | ||
FlutterVectorGraphicsListener(); | ||
_codec.decode(data, listener); | ||
return listener.toPicture(); | ||
} | ||
|
||
/// A widget that displays a vector_graphics formatted asset. | ||
/// | ||
/// A bytes loader class should not be constructed directly in a build method, | ||
/// if this is done the corresponding [VectorGraphic] widget may repeatedly | ||
/// reload the bytes. | ||
/// | ||
/// ```dart | ||
/// class MyVectorGraphic extends StatefulWidget { | ||
/// | ||
/// State<MyVectorGraphic> createState() => | ||
/// } | ||
/// | ||
/// class _MyVectorGraphicState extends State<MyVectorGraphic> { | ||
/// BytesLoader? loader; | ||
/// | ||
/// @override | ||
/// void initState() { | ||
/// super.initState(); | ||
/// loader = AssetBytesLoader(assetName: 'foobar', assetBundle: DefaultAssetBundle.of(context)); | ||
/// } | ||
/// | ||
/// @override | ||
/// Widget build(BuildContext context) { | ||
/// return VectorGraphic(bytesLoader: loader!); | ||
/// } | ||
/// } | ||
/// ``` | ||
class VectorGraphic extends StatefulWidget { | ||
const VectorGraphic({Key? key, required this.bytesLoader}) : super(key: key); | ||
|
||
final BytesLoader bytesLoader; | ||
|
||
@override | ||
State<VectorGraphic> createState() => _VectorGraphicsWidgetState(); | ||
} | ||
|
||
class _VectorGraphicsWidgetState extends State<VectorGraphic> { | ||
ui.Picture? _picture; | ||
|
||
@override | ||
void initState() { | ||
_loadAssetBytes(); | ||
super.initState(); | ||
} | ||
|
||
@override | ||
void didUpdateWidget(covariant VectorGraphic oldWidget) { | ||
if (oldWidget.bytesLoader != widget.bytesLoader) { | ||
_loadAssetBytes(); | ||
} | ||
super.didUpdateWidget(oldWidget); | ||
} | ||
|
||
@override | ||
void dispose() { | ||
_picture?.dispose(); | ||
_picture = null; | ||
super.dispose(); | ||
} | ||
|
||
void _loadAssetBytes() { | ||
widget.bytesLoader.loadBytes().then((ByteData data) { | ||
final ui.Picture picture = decodeVectorGraphics(data); | ||
setState(() { | ||
_picture?.dispose(); | ||
_picture = picture; | ||
}); | ||
}); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final ui.Picture? picture = _picture; | ||
if (picture == null) { | ||
return const SizedBox(); | ||
} | ||
return _RawVectorGraphicsWidget(picture: picture); | ||
} | ||
} | ||
|
||
/// An interface that can be implemented to support decoding vector graphic | ||
/// binary assets from different byte sources. | ||
/// | ||
/// A bytes loader class should not be constructed directly in a build method, | ||
/// if this is done the corresponding [VectorGraphic] widget may repeatedly | ||
/// reload the bytes. | ||
/// | ||
/// See also: | ||
/// * [AssetBytesLoader], for loading from the asset bundle. | ||
/// * [NetworkBytesLoader], for loading network bytes. | ||
abstract class BytesLoader { | ||
/// const constructor to allow subtypes to be const. | ||
const BytesLoader(); | ||
|
||
/// Load the byte data for a vector graphic binary asset. | ||
Future<ByteData> loadBytes(); | ||
} | ||
|
||
/// A controller for loading vector graphics data from an asset bundle. | ||
class AssetBytesLoader extends BytesLoader { | ||
/// Create a new [VectorGraphicsAssetController]. | ||
/// | ||
/// The default asset bundle can be acquired using [DefaultAssetBundle.of]. | ||
const AssetBytesLoader({ | ||
required this.assetName, | ||
this.packageName, | ||
required this.assetBundle, | ||
}); | ||
|
||
final String assetName; | ||
final String? packageName; | ||
final AssetBundle assetBundle; | ||
|
||
@override | ||
Future<ByteData> loadBytes() { | ||
return assetBundle.load(assetName); | ||
} | ||
} | ||
|
||
/// A controller for loading vector graphics data from over the network. | ||
class NetworkBytesLoader extends BytesLoader { | ||
const NetworkBytesLoader({ | ||
required this.url, | ||
this.headers, | ||
this.client, | ||
}); | ||
|
||
final Map<String, String>? headers; | ||
final Uri url; | ||
final HttpClient? client; | ||
|
||
@override | ||
Future<ByteData> loadBytes() async { | ||
final HttpClient currentClient = client ?? HttpClient(); | ||
final HttpClientRequest request = await currentClient.getUrl(url); | ||
headers?.forEach(request.headers.add); | ||
|
||
final HttpClientResponse response = await request.close(); | ||
if (response.statusCode != HttpStatus.ok) { | ||
await response.drain<List<int>>(<int>[]); | ||
throw Exception('Failed to load VectorGraphic: ${response.statusCode}'); | ||
} | ||
final Uint8List bytes = await consolidateHttpClientResponseBytes( | ||
response, | ||
); | ||
return bytes.buffer.asByteData(); | ||
} | ||
} | ||
|
||
class _RawVectorGraphicsWidget extends SingleChildRenderObjectWidget { | ||
const _RawVectorGraphicsWidget({Key? key, required this.picture}) | ||
: super(key: key); | ||
|
||
final ui.Picture picture; | ||
|
||
@override | ||
RenderObject createRenderObject(BuildContext context) { | ||
return _RenderVectorGraphics(picture); | ||
} | ||
|
||
@override | ||
void updateRenderObject( | ||
BuildContext context, covariant _RenderVectorGraphics renderObject) { | ||
renderObject.picture = picture; | ||
} | ||
} | ||
|
||
class _RenderVectorGraphics extends RenderProxyBox { | ||
_RenderVectorGraphics(this._picture); | ||
|
||
ui.Picture get picture => _picture; | ||
ui.Picture _picture; | ||
set picture(ui.Picture value) { | ||
if (identical(value, _picture)) { | ||
return; | ||
} | ||
_picture = value; | ||
markNeedsPaint(); | ||
} | ||
|
||
@override | ||
void paint(PaintingContext context, ui.Offset offset) { | ||
context.canvas.drawPicture(picture); | ||
} | ||
} |
This file contains 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
Oops, something went wrong.