diff --git a/assets/dart-ui/canvas_circle.png b/assets/dart-ui/canvas_circle.png new file mode 100644 index 00000000..b2fdbb6f Binary files /dev/null and b/assets/dart-ui/canvas_circle.png differ diff --git a/assets/dart-ui/canvas_line.png b/assets/dart-ui/canvas_line.png new file mode 100644 index 00000000..054a1f61 Binary files /dev/null and b/assets/dart-ui/canvas_line.png differ diff --git a/assets/dart-ui/canvas_oval.png b/assets/dart-ui/canvas_oval.png new file mode 100644 index 00000000..be90cb75 Binary files /dev/null and b/assets/dart-ui/canvas_oval.png differ diff --git a/assets/dart-ui/canvas_rect.png b/assets/dart-ui/canvas_rect.png new file mode 100644 index 00000000..6fe2aca9 Binary files /dev/null and b/assets/dart-ui/canvas_rect.png differ diff --git a/assets/dart-ui/canvas_rrect.png b/assets/dart-ui/canvas_rrect.png new file mode 100644 index 00000000..25e7ad17 Binary files /dev/null and b/assets/dart-ui/canvas_rrect.png differ diff --git a/assets/dart-ui/clip_path.png b/assets/dart-ui/clip_path.png new file mode 100644 index 00000000..fd4dfe35 Binary files /dev/null and b/assets/dart-ui/clip_path.png differ diff --git a/assets/dart-ui/clip_rect.png b/assets/dart-ui/clip_rect.png new file mode 100644 index 00000000..842f8908 Binary files /dev/null and b/assets/dart-ui/clip_rect.png differ diff --git a/assets/dart-ui/clip_rrect.png b/assets/dart-ui/clip_rrect.png new file mode 100644 index 00000000..f441f0a0 Binary files /dev/null and b/assets/dart-ui/clip_rrect.png differ diff --git a/assets/dart-ui/path_conic_to.png b/assets/dart-ui/path_conic_to.png new file mode 100644 index 00000000..934f6bfd Binary files /dev/null and b/assets/dart-ui/path_conic_to.png differ diff --git a/assets/dart-ui/path_cubic_to.png b/assets/dart-ui/path_cubic_to.png new file mode 100644 index 00000000..290ab45c Binary files /dev/null and b/assets/dart-ui/path_cubic_to.png differ diff --git a/assets/dart-ui/path_quadratic_to.png b/assets/dart-ui/path_quadratic_to.png new file mode 100644 index 00000000..9684dbd3 Binary files /dev/null and b/assets/dart-ui/path_quadratic_to.png differ diff --git a/assets/dart-ui/radius_circular.png b/assets/dart-ui/radius_circular.png new file mode 100644 index 00000000..a92ee360 Binary files /dev/null and b/assets/dart-ui/radius_circular.png differ diff --git a/assets/dart-ui/radius_elliptical.png b/assets/dart-ui/radius_elliptical.png new file mode 100644 index 00000000..ae9ca308 Binary files /dev/null and b/assets/dart-ui/radius_elliptical.png differ diff --git a/assets/dart-ui/rect_from_center.png b/assets/dart-ui/rect_from_center.png new file mode 100644 index 00000000..fb35dcc7 Binary files /dev/null and b/assets/dart-ui/rect_from_center.png differ diff --git a/assets/dart-ui/rect_from_circle.png b/assets/dart-ui/rect_from_circle.png new file mode 100644 index 00000000..35316abc Binary files /dev/null and b/assets/dart-ui/rect_from_circle.png differ diff --git a/assets/dart-ui/rect_from_ltrb.png b/assets/dart-ui/rect_from_ltrb.png new file mode 100644 index 00000000..c542990c Binary files /dev/null and b/assets/dart-ui/rect_from_ltrb.png differ diff --git a/assets/dart-ui/rect_from_ltwh.png b/assets/dart-ui/rect_from_ltwh.png new file mode 100644 index 00000000..3953650e Binary files /dev/null and b/assets/dart-ui/rect_from_ltwh.png differ diff --git a/assets/dart-ui/rect_from_points.png b/assets/dart-ui/rect_from_points.png new file mode 100644 index 00000000..61582e5b Binary files /dev/null and b/assets/dart-ui/rect_from_points.png differ diff --git a/packages/diagram_generator/lib/main.dart b/packages/diagram_generator/lib/main.dart index 8ccc316c..6716bc0d 100644 --- a/packages/diagram_generator/lib/main.dart +++ b/packages/diagram_generator/lib/main.dart @@ -95,12 +95,14 @@ Future main(List args) async { AnimationStatusValueDiagramStep(controller), AppBarDiagramStep(controller), ArcDiagramStep(controller), + BasicShapesStep(controller), BlendModeDiagramStep(controller), BottomNavigationBarDiagramStep(controller), BoxDecorationDiagramStep(controller), BoxFitDiagramStep(controller), CardDiagramStep(controller), CheckboxListTileDiagramStep(controller), + ClipDiagramStep(controller), ColorsDiagramStep(controller), ColumnDiagramStep(controller), ContainerDiagramStep(controller), diff --git a/packages/diagrams/lib/diagrams.dart b/packages/diagrams/lib/diagrams.dart index 945c1aac..3d304588 100644 --- a/packages/diagrams/lib/diagrams.dart +++ b/packages/diagrams/lib/diagrams.dart @@ -11,12 +11,14 @@ export 'src/animation_diagram.dart'; export 'src/animation_status_value.dart'; export 'src/app_bar.dart'; export 'src/arc_diagram.dart'; +export 'src/basic_shapes.dart'; export 'src/blend_mode.dart'; export 'src/bottom_navigation_bar.dart'; export 'src/box_decoration.dart'; export 'src/box_fit.dart'; export 'src/card.dart'; export 'src/checkbox_list_tile.dart'; +export 'src/clip_diagram.dart'; export 'src/colors.dart'; export 'src/column.dart'; export 'src/container.dart'; diff --git a/packages/diagrams/lib/src/arc_diagram.dart b/packages/diagrams/lib/src/arc_diagram.dart index f62d6f3a..0872a5be 100644 --- a/packages/diagrams/lib/src/arc_diagram.dart +++ b/packages/diagrams/lib/src/arc_diagram.dart @@ -10,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:vector_math/vector_math_64.dart' hide Colors; import 'diagram_step.dart'; +import 'utils.dart'; class Palette { const Palette({ @@ -183,29 +184,6 @@ void paintTextArc( } } -/// Paints [span] to [canvas] with a given offset and alignment. -void paintSpan( - Canvas canvas, - TextSpan span, { - required Offset offset, - Alignment alignment = Alignment.center, - TextAlign textAlign = TextAlign.center, -}) { - final TextPainter result = TextPainter( - textDirection: TextDirection.ltr, - text: span, - textAlign: textAlign, - ); - result.layout(); - result.paint( - canvas, - Offset( - offset.dx + (result.width / -2) + (alignment.x * result.width / 2), - offset.dy + (result.height / -2) + (alignment.y * result.height / 2), - ), - ); -} - void paintArrowHead( Canvas canvas, Offset center, @@ -238,35 +216,6 @@ void paintArrowHead( ); } -/// Similar to [paintSpan] but provides a default text style. -void paintLabel( - Canvas canvas, - String label, { - required Offset offset, - FontStyle style = FontStyle.normal, - FontWeight fontWeight = FontWeight.normal, - Color color = Colors.black45, - double fontSize = 14.0, - Alignment alignment = Alignment.center, - TextAlign textAlign = TextAlign.center, -}) { - paintSpan( - canvas, - TextSpan( - text: label, - style: TextStyle( - color: color, - fontWeight: fontWeight, - fontStyle: style, - fontSize: fontSize, - ), - ), - offset: offset, - alignment: alignment, - textAlign: textAlign, - ); -} - class ArcDiagramPainter extends CustomPainter { const ArcDiagramPainter({ required this.startAngle, @@ -397,9 +346,11 @@ class ArcDiagramPainter extends CustomPainter { 'rect', offset: rect.topLeft - const Offset(0, 4), alignment: Alignment.topRight, - color: palette.subtitle, - fontSize: 18.0, - fontWeight: FontWeight.bold, + style: TextStyle( + color: palette.subtitle, + fontSize: 18.0, + fontWeight: FontWeight.bold, + ), ); // Draw arrow at sweepAngle diff --git a/packages/diagrams/lib/src/basic_shapes.dart b/packages/diagrams/lib/src/basic_shapes.dart new file mode 100644 index 00000000..0be6c157 --- /dev/null +++ b/packages/diagrams/lib/src/basic_shapes.dart @@ -0,0 +1,1068 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart' hide Image; + +import 'diagram_step.dart'; +import 'utils.dart'; + +const double _kGridSize = 40.0; +const Color _kPrimaryColor = Color(0xFF3196E3); +const Color _kForegroundColor = Color(0xFF2548b0); +const Color _kHintColor = Colors.grey; +const EdgeInsets _kLabelPadding = EdgeInsets.all(8.0); +const TextStyle _kLabelStyle = TextStyle( + color: _kPrimaryColor, + fontWeight: FontWeight.bold, + fontSize: 16, +); + +class BasicShapesDiagram extends StatelessWidget implements DiagramMetadata { + const BasicShapesDiagram({ + required this.name, + required this.painter, + required this.width, + required this.height, + super.key, + }); + + @override + final String name; + final CustomPainter painter; + final double width; + final double height; + + @override + Widget build(BuildContext context) { + return CustomPaint( + painter: painter, + child: SizedBox( + width: width, + height: height, + ), + ); + } +} + +void _paintOffset( + Canvas canvas, + Offset offset, { + String? label, + Alignment alignment = Alignment.bottomCenter, + EdgeInsets padding = _kLabelPadding, + bool control = false, + Color color = _kPrimaryColor, +}) { + if (control) { + final Rect rect = Rect.fromCircle(center: offset, radius: 4.0); + canvas.drawRect(rect, Paint()..color = color); + } else { + canvas.drawCircle( + offset, + 4, + Paint()..color = color, + ); + } + if (label != null) { + paintLabel( + canvas, + label, + offset: offset, + padding: padding, + alignment: alignment, + style: _kLabelStyle, + ); + } +} + +void _paintXYPlane( + Canvas canvas, { + int width = 10, + int height = 6, + Color color = _kForegroundColor, +}) { + final double rightEdge = _kGridSize * width; + final double bottomEdge = _kGridSize * height; + const double arrowNudge = 8.0; + final double rightArrow = rightEdge - arrowNudge; + final double bottomArrow = bottomEdge - arrowNudge; + + final Paint paint = Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = 3.5; + + // Draw main lines going to the right and down + + canvas.drawPath( + Path() + ..moveTo(rightArrow, 0) + ..lineTo(0, 0) + ..lineTo(0, bottomArrow), + paint, + ); + + // Draw arrows + + paint + ..color = color + ..style = PaintingStyle.fill; + canvas.drawPath( + Path() + ..moveTo(rightArrow, -5) + ..lineTo(rightArrow + 10, 0) + ..lineTo(rightArrow, 5), + paint, + ); + canvas.drawPath( + Path() + ..moveTo(-5, bottomArrow) + ..lineTo(0, bottomArrow + 10) + ..lineTo(5, bottomArrow), + paint, + ); + + // Draw labels + + paintLabel( + canvas, + '0,0', + offset: const Offset(-4, -4), + alignment: Alignment.topLeft, + style: _kLabelStyle, + ); + + paintLabel( + canvas, + '+x', + offset: Offset(rightArrow, -8), + alignment: Alignment.topCenter, + style: _kLabelStyle, + ); + + paintLabel( + canvas, + '+y', + offset: Offset(-8, bottomArrow), + alignment: Alignment.centerLeft, + style: _kLabelStyle, + ); +} + +class LineDiagramPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + canvas.save(); + canvas.translate(56.0, 48.0); + + _paintXYPlane(canvas); + + final Paint paint = Paint() + ..strokeWidth = 5.0 + ..style = PaintingStyle.stroke + ..color = _kForegroundColor; + + final Offset start = const Offset(2, 4) * _kGridSize; + final Offset end = const Offset(8, 2) * _kGridSize; + + canvas.drawLine( + start, + end, + paint, + ); + + paintLabel( + canvas, + 'p1', + offset: start, + alignment: Alignment.topCenter, + padding: _kLabelPadding, + style: _kLabelStyle, + ); + + paintLabel( + canvas, + 'p2', + offset: end, + alignment: Alignment.topCenter, + padding: _kLabelPadding, + style: _kLabelStyle, + ); + + canvas.restore(); + } + + @override + bool shouldRepaint(LineDiagramPainter oldDelegate) => true; +} + +class RectConstructorDiagramPainter extends CustomPainter { + RectConstructorDiagramPainter({ + this.showLeft = false, + this.showTop = false, + this.showRight = false, + this.showBottom = false, + this.showWidth = false, + this.showHeight = false, + this.showTopLeft = false, + this.showBottomRight = false, + this.showCenter = false, + }); + + final bool showLeft; + final bool showTop; + final bool showRight; + final bool showBottom; + final bool showWidth; + final bool showHeight; + final bool showTopLeft; + final bool showBottomRight; + final bool showCenter; + + @override + void paint(Canvas canvas, Size size) { + canvas.save(); + canvas.translate(showBottom ? 90 : 60, showBottom ? 60 : 50); + + final Paint paint = Paint() + ..strokeWidth = 4.0 + ..style = PaintingStyle.stroke + ..color = _kForegroundColor; + + final Rect rect = Rect.fromPoints( + const Offset(2, 2) * _kGridSize, + const Offset(8, 5) * _kGridSize, + ); + final Offset topLeft = rect.topLeft; + final Offset bottomRight = rect.bottomRight; + + canvas.drawRect( + Rect.fromPoints(topLeft, bottomRight), + paint, + ); + + paint + ..color = _kPrimaryColor + ..style = PaintingStyle.stroke + ..strokeWidth = 4 + ..strokeJoin = StrokeJoin.round + ..strokeCap = StrokeCap.round; + + if (showLeft) { + paintLabel( + canvas, + 'left', + offset: Offset(topLeft.dx, -8), + alignment: Alignment.topCenter, + style: _kLabelStyle, + ); + canvas.drawLine( + Offset(topLeft.dx, 1), + Offset(topLeft.dx, 16), + paint, + ); + } + + if (showTop) { + paintLabel( + canvas, + 'top', + offset: Offset(-8, topLeft.dy), + alignment: Alignment.centerLeft, + style: _kLabelStyle, + ); + canvas.drawLine( + Offset(1, topLeft.dy), + Offset(16, topLeft.dy), + paint, + ); + } + + if (showRight) { + paintLabel( + canvas, + 'right', + offset: Offset(bottomRight.dx, -8), + alignment: Alignment.topCenter, + style: _kLabelStyle, + ); + canvas.drawLine( + Offset(bottomRight.dx, 1), + Offset(bottomRight.dx, 16), + paint, + ); + } + + if (showBottom) { + paintLabel( + canvas, + 'bottom', + offset: Offset(-8, bottomRight.dy), + alignment: Alignment.centerLeft, + style: _kLabelStyle, + ); + canvas.drawLine( + Offset(1, bottomRight.dy), + Offset(16, bottomRight.dy), + paint, + ); + } + + if (showHeight) { + paintLabel( + canvas, + 'height', + offset: rect.centerRight + const Offset(42, 0), + alignment: Alignment.centerRight, + style: _kLabelStyle, + ); + canvas.drawPath( + Path() + ..moveTo(bottomRight.dx + 18, topLeft.dy) + ..lineTo(bottomRight.dx + 24, topLeft.dy) + ..lineTo(bottomRight.dx + 24, bottomRight.dy) + ..lineTo(bottomRight.dx + 18, bottomRight.dy), + paint, + ); + } + + if (showWidth) { + paintLabel( + canvas, + 'width', + offset: rect.bottomCenter + const Offset(0, 42), + alignment: Alignment.bottomCenter, + style: _kLabelStyle, + ); + canvas.drawPath( + Path() + ..moveTo(topLeft.dx, bottomRight.dy + 18) + ..lineTo(topLeft.dx, bottomRight.dy + 24) + ..lineTo(bottomRight.dx, bottomRight.dy + 24) + ..lineTo(bottomRight.dx, bottomRight.dy + 18), + paint, + ); + } + + if (showTopLeft) { + paintLabel( + canvas, + 'a', + offset: topLeft, + alignment: Alignment.topLeft, + padding: _kLabelPadding, + style: _kLabelStyle, + ); + canvas.drawCircle(topLeft, 4, Paint()..color = paint.color); + } + + if (showBottomRight) { + paintLabel( + canvas, + 'b', + offset: bottomRight, + alignment: Alignment.topLeft, + padding: _kLabelPadding, + style: _kLabelStyle, + ); + canvas.drawCircle(bottomRight, 4, Paint()..color = paint.color); + } + + if (showCenter) { + paintLabel( + canvas, + 'center', + offset: rect.center, + alignment: Alignment.topCenter, + padding: _kLabelPadding, + style: _kLabelStyle, + ); + canvas.drawCircle(rect.center, 4, Paint()..color = paint.color); + } + + _paintXYPlane(canvas, width: 11, height: 7); + + canvas.restore(); + } + + @override + bool shouldRepaint(RectConstructorDiagramPainter oldDelegate) => true; +} + +class OvalDiagramPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + void drawRect(Rect rect, PaintingStyle style) { + final Paint paint = Paint() + ..strokeWidth = 4.0 + ..style = PaintingStyle.stroke + ..color = _kHintColor; + + canvas.drawRect( + rect, + paint, + ); + + paintLabel( + canvas, + 'rect', + offset: rect.topLeft + const Offset(0, -8), + alignment: Alignment.topRight, + style: _kLabelStyle.copyWith(color: paint.color), + ); + + paint + ..color = _kForegroundColor + ..style = style; + + if (style == PaintingStyle.stroke) { + canvas.drawOval( + rect.deflate(paint.strokeWidth), + paint, + ); + paintLabel( + canvas, + '$style', + offset: rect.center, + style: _kLabelStyle.copyWith( + color: _kForegroundColor, + fontSize: 12, + ), + ); + } else { + canvas.saveLayer(null, Paint()); + canvas.drawOval( + rect.deflate(paint.strokeWidth / 2), + paint, + ); + // Punch a hole in the solid oval with dstOut + canvas.saveLayer(null, Paint()..blendMode = BlendMode.dstOut); + paintLabel( + canvas, + '$style', + offset: rect.center, + style: _kLabelStyle.copyWith( + color: Colors.white, + fontSize: 12, + ), + ); + canvas.restore(); + canvas.restore(); + } + } + + drawRect( + const Rect.fromLTRB( + 64, + 64, + 64 * 4, + 64 * 5, + ), + PaintingStyle.stroke, + ); + + drawRect( + const Rect.fromLTRB( + 64 * 5, + 64 * 1.5, + 64 * 9, + 64 * 4.5, + ), + PaintingStyle.fill, + ); + } + + @override + bool shouldRepaint(OvalDiagramPainter oldDelegate) => true; +} + +class RectDiagramPainter extends CustomPainter { + RectDiagramPainter({this.radius = 0.0, this.label = 'rect'}); + + final double radius; + final String label; + + @override + void paint(Canvas canvas, Size size) { + void drawRect(RRect rrect, PaintingStyle style) { + final Paint paint = Paint() + ..strokeWidth = 4.0 + ..style = PaintingStyle.stroke + ..color = _kForegroundColor; + + canvas.drawRRect( + rrect, + paint, + ); + + paintLabel( + canvas, + label, + offset: rrect.outerRect.topLeft + const Offset(0, -8), + alignment: Alignment.topRight, + style: _kLabelStyle, + ); + + paint + ..color = _kForegroundColor + ..style = style; + + if (style == PaintingStyle.stroke) { + canvas.drawRRect( + rrect, + paint, + ); + paintLabel( + canvas, + '$style', + offset: rrect.center, + style: _kLabelStyle.copyWith( + color: _kForegroundColor, + fontSize: 12, + ), + ); + } else { + canvas.saveLayer(null, Paint()); + canvas.drawRRect(rrect, paint); + // Punch a hole in the solid rrect with dstOut + canvas.saveLayer(null, Paint()..blendMode = BlendMode.dstOut); + paintLabel( + canvas, + '$style', + offset: rrect.center, + style: _kLabelStyle.copyWith( + color: Colors.white, + fontSize: 12, + ), + ); + canvas.restore(); + canvas.restore(); + } + } + + drawRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB( + 64, + 64, + 64 * 4, + 64 * 5, + ), + Radius.circular(radius), + ), + PaintingStyle.stroke, + ); + + drawRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB( + 64 * 5, + 64 * 1.5, + 64 * 9, + 64 * 4.5, + ), + Radius.circular(radius), + ), + PaintingStyle.fill, + ); + } + + @override + bool shouldRepaint(RectDiagramPainter oldDelegate) => true; +} + +class CircleDiagramPainter extends CustomPainter { + CircleDiagramPainter({this.square = false}); + + final bool square; + + @override + void paint(Canvas canvas, Size size) { + canvas.save(); + canvas.translate(56, 48); + + _paintXYPlane(canvas, width: 13, height: 8); + + final Paint paint = Paint(); + + final Offset center = const Offset(6.5, 4) * _kGridSize; + final Rect rect = Rect.fromCircle( + center: center, + radius: 3 * _kGridSize, + ); + + paintLabel( + canvas, + 'center', + offset: center + const Offset(0, -8), + alignment: Alignment.topCenter, + style: _kLabelStyle, + ); + + final double cx = rect.left + rect.width / 4; + final double cy = rect.center.dy; + paintLabel( + canvas, + 'radius', + offset: Offset(cx, cy + 8), + alignment: Alignment.bottomCenter, + style: _kLabelStyle, + ); + + paint + ..color = _kPrimaryColor + ..style = PaintingStyle.stroke + ..strokeWidth = 5 + ..strokeJoin = StrokeJoin.round + ..strokeCap = StrokeCap.round; + + canvas.drawLine( + rect.center, + rect.centerLeft, + paint, + ); + + paint + ..style = PaintingStyle.fill + ..color = _kPrimaryColor; + + canvas.drawCircle( + center, + 4, + paint, + ); + + paint + ..strokeWidth = 5 + ..style = PaintingStyle.stroke + ..color = _kForegroundColor + ..strokeJoin = StrokeJoin.miter; + + if (square) { + canvas.drawRect( + rect, + paint, + ); + } else { + canvas.drawCircle( + center, + rect.width / 2, + paint, + ); + } + + canvas.restore(); + } + + @override + bool shouldRepaint(CircleDiagramPainter oldDelegate) => true; +} + +class ConicToDiagramPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final double x = size.width * 0.1; + final double y = (size.height * 0.9) - 15.0; + final double x2 = size.width * 0.9; + final double y2 = y; + final double x1 = (x + x2) / 2; + final double y1 = size.height * 0.1; + + final Paint paint = Paint() + ..color = _kHintColor + ..strokeWidth = 5.0 + ..style = PaintingStyle.stroke; + + canvas.drawPath( + Path() + ..moveTo(x, y) + ..conicTo(x1, y1, x2, y2, 2.0), + paint, + ); + + canvas.drawPath( + Path() + ..moveTo(x, y) + ..conicTo(x1, y1, x2, y2, 0.5), + paint, + ); + + paint.color = _kForegroundColor; + + canvas.drawPath( + Path() + ..moveTo(x, y) + ..conicTo(x1, y1, x2, y2, 1), + paint, + ); + + _paintOffset( + canvas, + Offset(x1, y1), + label: 'x1,y1', + control: true, + ); + _paintOffset(canvas, Offset(x2, y2), label: 'x2,y2'); + + paintLabel( + canvas, + 'w = 2', + offset: Offset(size.width / 2, size.height * 0.353 + 8), + style: _kLabelStyle.copyWith(color: _kHintColor), + alignment: Alignment.bottomCenter, + ); + + paintLabel( + canvas, + 'w = 1', + offset: Offset(size.width / 2, size.height * 0.48 + 8), + style: _kLabelStyle.copyWith(color: _kForegroundColor), + alignment: Alignment.bottomCenter, + ); + + paintLabel( + canvas, + 'w = 0.5', + offset: Offset(size.width / 2, size.height * 0.605 + 8), + style: _kLabelStyle.copyWith(color: _kHintColor), + alignment: Alignment.bottomCenter, + ); + } + + @override + bool shouldRepaint(ConicToDiagramPainter oldDelegate) => true; +} + +class QuadraticToDiagramPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final double x = size.width * 0.1; + final double y = (size.height * 0.9) - 15.0; + final double x2 = size.width * 0.9; + final double y2 = y; + final double x1 = (x + x2) / 2; + final double y1 = size.height * 0.1; + + final Paint paint = Paint() + ..color = _kForegroundColor + ..strokeWidth = 5.0 + ..style = PaintingStyle.stroke; + + canvas.drawPath( + Path() + ..moveTo(x, y) + ..quadraticBezierTo(x1, y1, x2, y2), + paint, + ); + + _paintOffset( + canvas, + Offset(x1, y1), + label: 'x1,y1', + control: true, + ); + _paintOffset(canvas, Offset(x2, y2), label: 'x2,y2'); + } + + @override + bool shouldRepaint(QuadraticToDiagramPainter oldDelegate) => true; +} + +class CubicToDiagramPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final double x = size.width * 0.1; + final double y = size.height * 0.5; + final double x1 = size.width * 0.3; + final double y1 = size.height * 0.15; + final double x2 = size.width * 0.7; + final double y2 = size.height * 0.85; + final double x3 = size.width * 0.9; + final double y3 = size.height * 0.5; + + final Paint paint = Paint() + ..color = _kHintColor + ..strokeWidth = 3.0 + ..style = PaintingStyle.stroke; + + canvas.drawLine( + Offset(x, y), + Offset(x1, y1), + paint, + ); + + canvas.drawLine( + Offset(x2, y2), + Offset(x3, y3), + paint, + ); + + paint + ..color = _kForegroundColor + ..strokeWidth = 5.0; + + canvas.drawPath( + Path() + ..moveTo(x, y) + ..cubicTo(x1, y1, x2, y2, x3, y3), + paint, + ); + + _paintOffset( + canvas, + Offset(x1, y1), + label: 'x1,y1', + control: true, + alignment: Alignment.topCenter, + ); + _paintOffset( + canvas, + Offset(x2, y2), + label: 'x2,y2', + control: true, + ); + _paintOffset( + canvas, + Offset(x3, y3), + label: 'x3,y3', + alignment: Alignment.topCenter, + ); + } + + @override + bool shouldRepaint(CubicToDiagramPainter oldDelegate) => true; +} + +class RadiusDiagramPainter extends CustomPainter { + RadiusDiagramPainter({required this.radius}); + + final Radius radius; + + @override + void paint(Canvas canvas, Size size) { + final Rect rect = Rect.fromLTRB( + size.width * 0.6 - radius.x, + size.height * 0.6 - radius.y, + size.width + 200, + size.height + 200, + ); + final RRect rrect = RRect.fromRectAndRadius(rect, radius); + final Offset center = rect.topLeft + Offset(radius.x, radius.y); + + canvas.saveLayer(null, Paint()); + canvas.drawLine( + center, + center - Offset(radius.x, 0), + Paint() + ..color = _kPrimaryColor + ..strokeWidth = 5, + ); + paintLabel( + canvas, + radius.x == radius.y ? 'radius' : 'x', + offset: Offset(rect.left + radius.x / 2, rect.top + radius.y), + style: _kLabelStyle, + alignment: Alignment.bottomCenter, + padding: _kLabelPadding, + ); + + if (radius.x != radius.y) { + canvas.drawLine( + center, + center - Offset(0, radius.y), + Paint() + ..color = _kPrimaryColor + ..strokeWidth = 5, + ); + paintLabel( + canvas, + 'y', + offset: Offset(rect.left + radius.x, rect.top + radius.y / 2), + style: _kLabelStyle, + alignment: Alignment.centerRight, + padding: _kLabelPadding, + ); + } + + _paintOffset(canvas, center, color: _kForegroundColor); + + canvas.drawRRect( + rrect, + Paint() + ..color = _kForegroundColor + ..strokeWidth = 6.0 + ..style = PaintingStyle.stroke, + ); + + canvas.drawPaint( + Paint() + ..shader = ui.Gradient.linear( + Offset.zero, + Offset(0, size.height), + [ + Colors.white, + Colors.white.withOpacity(0), + ], + [1 - 64 / size.height, 1.0], + ) + ..blendMode = BlendMode.dstIn, + ); + + canvas.drawPaint( + Paint() + ..shader = ui.Gradient.linear( + Offset.zero, + Offset(size.width, 0), + [ + Colors.white, + Colors.white.withOpacity(0), + ], + [1 - 64 / size.width, 1.0], + ) + ..blendMode = BlendMode.dstIn, + ); + + canvas.restore(); + } + + @override + bool shouldRepaint(RadiusDiagramPainter oldDelegate) => true; +} + +class BasicShapesStep extends DiagramStep { + BasicShapesStep(super.controller); + + @override + final String category = 'dart-ui'; + + @override + Future> get diagrams async { + return [ + BasicShapesDiagram( + name: 'canvas_line', + painter: LineDiagramPainter(), + width: 500, + height: 325, + ), + BasicShapesDiagram( + name: 'canvas_rect', + painter: RectDiagramPainter(), + width: 640, + height: 384, + ), + BasicShapesDiagram( + name: 'canvas_rrect', + painter: RectDiagramPainter( + label: 'rrect', + radius: 24, + ), + width: 640, + height: 384, + ), + BasicShapesDiagram( + name: 'rect_from_ltrb', + painter: RectConstructorDiagramPainter( + showLeft: true, + showTop: true, + showRight: true, + showBottom: true, + ), + width: 580, + height: 380, + ), + BasicShapesDiagram( + name: 'rect_from_ltwh', + painter: RectConstructorDiagramPainter( + showLeft: true, + showTop: true, + showWidth: true, + showHeight: true, + ), + width: 550, + height: 370, + ), + BasicShapesDiagram( + name: 'rect_from_points', + painter: RectConstructorDiagramPainter( + showTopLeft: true, + showBottomRight: true, + ), + width: 550, + height: 370, + ), + BasicShapesDiagram( + name: 'rect_from_center', + painter: RectConstructorDiagramPainter( + showWidth: true, + showHeight: true, + showCenter: true, + ), + width: 550, + height: 370, + ), + BasicShapesDiagram( + name: 'rect_from_circle', + painter: CircleDiagramPainter(square: true), + width: 625, + height: 410, + ), + BasicShapesDiagram( + name: 'canvas_oval', + painter: OvalDiagramPainter(), + width: 640, + height: 384, + ), + BasicShapesDiagram( + name: 'canvas_circle', + painter: CircleDiagramPainter(), + width: 625, + height: 410, + ), + BasicShapesDiagram( + name: 'path_conic_to', + painter: ConicToDiagramPainter(), + width: 600, + height: 350, + ), + BasicShapesDiagram( + name: 'path_quadratic_to', + painter: QuadraticToDiagramPainter(), + width: 600, + height: 350, + ), + BasicShapesDiagram( + name: 'path_cubic_to', + painter: CubicToDiagramPainter(), + width: 500, + height: 350, + ), + BasicShapesDiagram( + name: 'radius_circular', + painter: RadiusDiagramPainter( + radius: const Radius.circular(96), + ), + width: 500, + height: 350, + ), + BasicShapesDiagram( + name: 'radius_elliptical', + painter: RadiusDiagramPainter( + radius: const Radius.elliptical(144, 96), + ), + width: 500, + height: 350, + ), + ]; + } + + @override + Future generateDiagram(BasicShapesDiagram diagram) async { + controller.builder = (BuildContext context) => diagram; + return controller.drawDiagramToFile(File('${diagram.name}.png')); + } +} diff --git a/packages/diagrams/lib/src/blend_mode.dart b/packages/diagrams/lib/src/blend_mode.dart index 8df3e048..8f5fe61b 100644 --- a/packages/diagrams/lib/src/blend_mode.dart +++ b/packages/diagrams/lib/src/blend_mode.dart @@ -11,6 +11,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' hide Image; import 'diagram_step.dart'; +import 'utils.dart'; final GlobalKey key = GlobalKey(); @@ -26,25 +27,6 @@ const String kMonospaceFont = 'Courier New'; Image? destinationImage, sourceImage, gridImage; int pageIndex = 0; -Future getImage(ImageProvider provider) { - final Completer completer = Completer(); - final ImageStream stream = provider.resolve(ImageConfiguration.empty); - late final ImageStreamListener listener; - listener = ImageStreamListener( - (ImageInfo image, bool sync) { - completer.complete(image.image); - stream.removeListener(listener); - }, - onError: (Object error, StackTrace? stack) { - print(error); - throw error; // ignore: only_throw_errors - }, - ); - - stream.addListener(listener); - return completer.future; -} - class BlendModeDiagram extends StatelessWidget implements DiagramMetadata { const BlendModeDiagram(this.mode, {super.key}); diff --git a/packages/diagrams/lib/src/clip_diagram.dart b/packages/diagrams/lib/src/clip_diagram.dart new file mode 100644 index 00000000..5167a425 --- /dev/null +++ b/packages/diagrams/lib/src/clip_diagram.dart @@ -0,0 +1,209 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart' hide Image; + +import 'diagram_step.dart'; +import 'utils.dart'; + +const ImageProvider _backgroundImageProvider = + ExactAssetImage('assets/blend_mode_destination.jpeg', package: 'diagrams'); + +ui.Image? _backgroundImage; + +class ClipDiagram extends StatelessWidget implements DiagramMetadata { + const ClipDiagram({required this.name, required this.painter, super.key}); + + @override + final String name; + final CustomPainter painter; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.white, + child: CustomPaint( + painter: painter, + child: const SizedBox( + width: 700, + height: 400, + ), + ), + ); + } +} + +void _drawBackground(ui.Canvas canvas, ui.Size size) { + final FittedSizes sizes = applyBoxFit( + BoxFit.cover, + Size( + _backgroundImage!.width.toDouble(), + _backgroundImage!.height.toDouble(), + ), + size, + ); + canvas.drawImageRect( + _backgroundImage!, + Offset.zero & sizes.source, + Offset.zero & sizes.destination, + Paint(), + ); +} + +class ClipRectPainter extends CustomPainter { + @override + void paint(ui.Canvas canvas, ui.Size size) { + canvas.saveLayer(Offset.zero & size, Paint()..color = Colors.white30); + _drawBackground(canvas, size); + canvas.restore(); + + const Rect rect = Rect.fromLTWH(100, 100, 500.0, 200.0); + + canvas.save(); + canvas.clipRect(rect); + _drawBackground(canvas, size); + canvas.restore(); + + final Paint paint = Paint() + ..style = PaintingStyle.stroke + ..color = Colors.white + ..strokeWidth = 3.0; + canvas.drawRect(rect, paint); + } + + @override + bool shouldRepaint(ClipRectPainter oldDelegate) => false; +} + +class ClipRRectPainter extends CustomPainter { + @override + void paint(ui.Canvas canvas, ui.Size size) { + canvas.saveLayer(Offset.zero & size, Paint()..color = Colors.white30); + _drawBackground(canvas, size); + canvas.restore(); + + const Rect rect = Rect.fromLTWH(100, 100, 500.0, 200.0); + final RRect rrect = + RRect.fromRectAndRadius(rect, const Radius.circular(32.0)); + + canvas.save(); + canvas.clipRRect(rrect); + _drawBackground(canvas, size); + canvas.restore(); + + final Paint paint = Paint() + ..style = PaintingStyle.stroke + ..color = Colors.white + ..strokeWidth = 3.0; + canvas.drawRRect(rrect, paint); + } + + @override + bool shouldRepaint(ClipRectPainter oldDelegate) => false; +} + +class ClipPathPainter extends CustomPainter { + @override + void paint(ui.Canvas canvas, ui.Size size) { + canvas.saveLayer(Offset.zero & size, Paint()..color = Colors.white30); + _drawBackground(canvas, size); + canvas.restore(); + + final double cx = size.width / 2; + final double cy = size.height / 2; + final double leftAnchorX = cx - 150; + final double rightAnchorX = cx + 150; + final double middleAnchorY = cy - 25; + final double topAnchorY = cy - 85; + final double bottomAnchorY = cy + 150; + const double bottomControl = 20.0; + const double topControl = 60.0; + const double middleControl = 75.0; + + final Path path = Path() + ..moveTo(cx, bottomAnchorY) + ..cubicTo( + cx - bottomControl, + bottomAnchorY, + leftAnchorX, + middleAnchorY + middleControl, + leftAnchorX, + middleAnchorY, + ) + ..cubicTo( + leftAnchorX, + middleAnchorY - middleControl, + cx - topControl, + topAnchorY - topControl, + cx, + topAnchorY, + ) + ..cubicTo( + cx + topControl, + topAnchorY - topControl, + rightAnchorX, + middleAnchorY - middleControl, + rightAnchorX, + middleAnchorY, + ) + ..cubicTo( + rightAnchorX, + middleAnchorY + middleControl, + cx + bottomControl, + bottomAnchorY, + cx, + bottomAnchorY, + ); + + canvas.save(); + canvas.clipPath(path); + _drawBackground(canvas, size); + canvas.restore(); + + final Paint paint = Paint() + ..style = PaintingStyle.stroke + ..color = Colors.white + ..strokeWidth = 3.0; + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(ClipRectPainter oldDelegate) => false; +} + +class ClipDiagramStep extends DiagramStep { + ClipDiagramStep(super.controller); + + @override + final String category = 'dart-ui'; + + @override + Future> get diagrams async { + _backgroundImage ??= await getImage(_backgroundImageProvider); + return [ + ClipDiagram( + name: 'clip_rect', + painter: ClipRectPainter(), + ), + ClipDiagram( + name: 'clip_rrect', + painter: ClipRRectPainter(), + ), + ClipDiagram( + name: 'clip_path', + painter: ClipPathPainter(), + ), + ]; + } + + @override + Future generateDiagram(ClipDiagram diagram) async { + controller.builder = (BuildContext context) => diagram; + return controller.drawDiagramToFile(File('${diagram.name}.png')); + } +} diff --git a/packages/diagrams/lib/src/utils.dart b/packages/diagrams/lib/src/utils.dart index 970723cf..e83dcbcf 100644 --- a/packages/diagrams/lib/src/utils.dart +++ b/packages/diagrams/lib/src/utils.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; +import 'dart:ui' as ui; + import 'package:flutter/material.dart'; /// This defines a colored placeholder with padding, used to represent a @@ -151,3 +154,76 @@ class LabelPainter extends CustomPainter { @override bool hitTest(Offset position) => false; } + +/// Resolves [provider] and returns an [ui.Image] that can be used in a +/// [CustomPainter]. +Future getImage(ImageProvider provider) { + final Completer completer = Completer(); + final ImageStream stream = provider.resolve(ImageConfiguration.empty); + late final ImageStreamListener listener; + listener = ImageStreamListener( + (ImageInfo image, bool sync) { + completer.complete(image.image); + stream.removeListener(listener); + }, + onError: (Object error, StackTrace? stack) { + print(error); + throw error; // ignore: only_throw_errors + }, + ); + + stream.addListener(listener); + return completer.future; +} + +/// Paints [span] to [canvas] with a given offset and alignment. +void paintSpan( + Canvas canvas, + TextSpan span, { + required Offset offset, + Alignment alignment = Alignment.center, + EdgeInsets padding = EdgeInsets.zero, + TextAlign textAlign = TextAlign.center, +}) { + final TextPainter result = TextPainter( + textDirection: TextDirection.ltr, + text: span, + textAlign: textAlign, + ); + result.layout(); + final double width = result.width + padding.horizontal; + final double height = result.height + padding.vertical; + result.paint( + canvas, + Offset( + padding.left + offset.dx + (width / -2) + (alignment.x * width / 2), + padding.top + offset.dy + (height / -2) + (alignment.y * height / 2), + ), + ); +} + +/// Similar to [paintSpan] but provides a default text style. +void paintLabel( + Canvas canvas, + String label, { + required Offset offset, + Alignment alignment = Alignment.center, + EdgeInsets padding = EdgeInsets.zero, + TextAlign textAlign = TextAlign.center, + TextStyle? style, +}) { + paintSpan( + canvas, + TextSpan( + text: label, + style: const TextStyle( + color: Colors.black, + fontSize: 14.0, + ).merge(style ?? const TextStyle()), + ), + offset: offset, + alignment: alignment, + padding: padding, + textAlign: textAlign, + ); +}