From d5c2cfcb56b85eecb21e96392c5081197ed52a05 Mon Sep 17 00:00:00 2001 From: ilopX Date: Mon, 16 May 2022 22:08:17 +0300 Subject: [PATCH 1/5] Impl project "Tool Panel". --- bin/main.dart | 4 +- .../tool_panel_factory/app/app.dart | 21 ++++++ .../tool_panel_factory/app/shapes.dart | 25 +++++++ .../tool_panel_factory/app/tools.dart | 40 +++++++++++ .../factories/circle_factory.dart | 18 +++++ .../factories/line_factory.dart | 18 +++++ .../factories/star_factory.dart | 18 +++++ .../factories/triangle_factory.dart | 18 +++++ .../factories/txt_factory.dart | 19 +++++ .../tool_panel_factory/main.dart | 55 ++++++++++++++ .../mixin/icon_box_mixin.dart | 71 +++++++++++++++++++ .../tool_panel_factory/pattern/shape.dart | 30 ++++++++ .../pattern/tool_factory.dart | 9 +++ .../shapes/circle_shape.dart | 36 ++++++++++ .../tool_panel_factory/shapes/line_shape.dart | 37 ++++++++++ .../tool_panel_factory/shapes/star_shape.dart | 52 ++++++++++++++ .../shapes/triangle_shape.dart | 49 +++++++++++++ .../tool_panel_factory/shapes/txt_shape.dart | 51 +++++++++++++ .../widgets/colors_tool_bar.dart | 35 +++++++++ .../widgets/drawing_board.dart | 39 ++++++++++ .../widgets/factories_tool_bar.dart | 46 ++++++++++++ .../independent/event_listenable_builder.dart | 44 ++++++++++++ .../widgets/independent/hove.dart | 47 ++++++++++++ .../widgets/independent/tool_bar.dart | 62 ++++++++++++++++ .../widgets/independent/tool_button.dart | 40 +++++++++++ .../widgets/shape_widget.dart | 39 ++++++++++ .../widgets/tool_panel.dart | 25 +++++++ 27 files changed, 947 insertions(+), 1 deletion(-) create mode 100644 patterns/abstract_factory/tool_panel_factory/app/app.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/app/shapes.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/app/tools.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/factories/circle_factory.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/factories/line_factory.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/factories/star_factory.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/factories/triangle_factory.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/factories/txt_factory.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/main.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/mixin/icon_box_mixin.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/pattern/shape.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/pattern/tool_factory.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/shapes/circle_shape.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/shapes/line_shape.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/shapes/star_shape.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/shapes/triangle_shape.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/shapes/txt_shape.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/widgets/colors_tool_bar.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/widgets/drawing_board.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/widgets/factories_tool_bar.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/widgets/independent/event_listenable_builder.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/widgets/independent/hove.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/widgets/independent/tool_bar.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/widgets/independent/tool_button.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/widgets/shape_widget.dart create mode 100644 patterns/abstract_factory/tool_panel_factory/widgets/tool_panel.dart diff --git a/bin/main.dart b/bin/main.dart index 935d592..1f3c009 100644 --- a/bin/main.dart +++ b/bin/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../patterns/abstract_factory/tool_panel_factory/main.dart'; import '../patterns/observer/subscriber_flutter_widget/main.dart'; import '../patterns/adapter/flutter_adapter/main.dart'; import '../patterns/memento/memento_editor/main.dart'; @@ -13,11 +14,12 @@ class MyApp extends StatelessWidget { return MaterialApp( title: 'Refactoring Guru: Flutter launcher', theme: ThemeData(primarySwatch: Colors.pink), - initialRoute: '/memento/flutter_memento_editor', + initialRoute: '/abstract_factory/tool_panel', routes: { '/observer/subscriber_flutter_widget': (_) => SubscriberFlutterApp(), '/adapter/flutter_adapter': (_) => FlutterAdapterApp(), '/memento/flutter_memento_editor': (_) => FlutterMementoEditorApp(), + '/abstract_factory/tool_panel': (_) => WToolPanelApp(), }, ); } diff --git a/patterns/abstract_factory/tool_panel_factory/app/app.dart b/patterns/abstract_factory/tool_panel_factory/app/app.dart new file mode 100644 index 0000000..5613017 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/app/app.dart @@ -0,0 +1,21 @@ +import '../app/tools.dart'; +import 'shapes.dart'; + +class App { + final Tools tools; + final Shapes shapes; + + App({ + required this.tools, + required this.shapes, + }); + + void addShape(double x, double y) { + final activeColor = tools.activeColor.value; + final activeFactory = tools.activeFactory.value!; + + final newShape = activeFactory.createShape(x, y, activeColor); + newShape.centerToFit(); + shapes.add(newShape); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/app/shapes.dart b/patterns/abstract_factory/tool_panel_factory/app/shapes.dart new file mode 100644 index 0000000..da28a47 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/app/shapes.dart @@ -0,0 +1,25 @@ +import 'dart:collection'; + +import 'package:flutter/foundation.dart'; + +import '../pattern/shape.dart'; + +class Shapes with IterableMixin { + final List _shapes; + + Shapes(this._shapes); + + void add(Shape shape) { + _shapes.add(shape); + onAddShapeEvent._emit(); + } + + @override + Iterator get iterator => _shapes.iterator; + + final onAddShapeEvent = Event(); +} + +class Event extends ChangeNotifier { + void _emit() => notifyListeners(); +} diff --git a/patterns/abstract_factory/tool_panel_factory/app/tools.dart b/patterns/abstract_factory/tool_panel_factory/app/tools.dart new file mode 100644 index 0000000..c0cf9c7 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/app/tools.dart @@ -0,0 +1,40 @@ +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; + +import '../mixin/icon_box_mixin.dart'; +import '../pattern/tool_factory.dart'; + +class Tools { + final List factories; + final List colors; + + final activeFactory = ValueNotifier(null); + + final activeColor = ValueNotifier(Color(0x0FFFFFFFF)); + + Future get iconsReady => _iconsInitCompleter.future; + + Tools({required this.factories, required this.colors}) { + _initIconsFromShapes(); + + if (factories.isNotEmpty) { + activeFactory.value = factories.first; + } + + if (colors.isNotEmpty) { + activeColor.value = colors.first; + } + } + + final _iconsInitCompleter = Completer(); + + void _initIconsFromShapes() async { + await Future.wait([ + for (final factory in factories) + (factory as IconBoxMixin).updateIcon(activeColor.value), + ]); + _iconsInitCompleter.complete(Future.value(true)); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/factories/circle_factory.dart b/patterns/abstract_factory/tool_panel_factory/factories/circle_factory.dart new file mode 100644 index 0000000..529e311 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/factories/circle_factory.dart @@ -0,0 +1,18 @@ +import 'dart:ui'; + +import '../mixin/icon_box_mixin.dart'; +import '../pattern/tool_factory.dart'; +import '../shapes/circle_shape.dart'; +import '../pattern/shape.dart'; + +class CircleFactory extends FactoryTool with IconBoxMixin { + @override + Shape createShape(double x, double y, Color color) { + return CircleShape( + radius: 50, + x: x, + y: y, + color: color, + ); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/factories/line_factory.dart b/patterns/abstract_factory/tool_panel_factory/factories/line_factory.dart new file mode 100644 index 0000000..aa7e01f --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/factories/line_factory.dart @@ -0,0 +1,18 @@ +import 'dart:ui'; + +import '../mixin/icon_box_mixin.dart'; +import '../pattern/tool_factory.dart'; +import '../pattern/shape.dart'; +import '../shapes/line_shape.dart'; + +class LineFactory extends FactoryTool with IconBoxMixin { + @override + Shape createShape(double x, double y, Color color) { + return LineShape( + length: 100, + x: x, + y: y, + color: color, + ); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/factories/star_factory.dart b/patterns/abstract_factory/tool_panel_factory/factories/star_factory.dart new file mode 100644 index 0000000..794bcdd --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/factories/star_factory.dart @@ -0,0 +1,18 @@ +import 'dart:ui'; + +import '../mixin/icon_box_mixin.dart'; +import '../pattern/tool_factory.dart'; +import '../pattern/shape.dart'; +import '../shapes/star_shape.dart'; + +class StarFactory extends FactoryTool with IconBoxMixin { + @override + Shape createShape(double x, double y, Color color) { + return StarShape( + radius: 80, + x: x, + y: y, + color: color, + ); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/factories/triangle_factory.dart b/patterns/abstract_factory/tool_panel_factory/factories/triangle_factory.dart new file mode 100644 index 0000000..1cac49c --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/factories/triangle_factory.dart @@ -0,0 +1,18 @@ +import 'dart:ui'; + +import '../mixin/icon_box_mixin.dart'; +import '../pattern/tool_factory.dart'; +import '../pattern/shape.dart'; +import '../shapes/triangle_shape.dart'; + +class TriangleFactory extends FactoryTool with IconBoxMixin { + @override + Shape createShape(double x, double y, Color color) { + return TriangleShape( + sideLength: 120, + x: x, + y: y, + color: color, + ); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/factories/txt_factory.dart b/patterns/abstract_factory/tool_panel_factory/factories/txt_factory.dart new file mode 100644 index 0000000..011b093 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/factories/txt_factory.dart @@ -0,0 +1,19 @@ +import 'dart:ui'; + +import '../mixin/icon_box_mixin.dart'; +import '../pattern/tool_factory.dart'; +import '../shapes/txt_shape.dart'; +import '../pattern/shape.dart'; + +class TxtFactory extends FactoryTool with IconBoxMixin { + @override + Shape createShape(double x, double y, Color color) { + return Txt( + text: 'Text', + fontSize: 100, + x: x, + y: y, + color: color, + ); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/main.dart b/patterns/abstract_factory/tool_panel_factory/main.dart new file mode 100644 index 0000000..f20c48b --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/main.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; + +import 'app/app.dart'; +import 'app/shapes.dart'; +import 'app/tools.dart'; +import 'factories/circle_factory.dart'; +import 'factories/line_factory.dart'; +import 'factories/star_factory.dart'; +import 'factories/triangle_factory.dart'; +import 'factories/txt_factory.dart'; +import 'widgets/drawing_board.dart'; +import 'widgets/tool_panel.dart'; + +class WToolPanelApp extends StatefulWidget { + const WToolPanelApp({Key? key}) : super(key: key); + + @override + _WToolPanelAppState createState() => _WToolPanelAppState(); +} + +class _WToolPanelAppState extends State { + final app = App( + shapes: Shapes([]), + tools: Tools( + factories: [ + TxtFactory(), + LineFactory(), + CircleFactory(), + TriangleFactory(), + StarFactory(), + ], + colors: [ + Colors.white, + Colors.green, + Colors.blue, + Colors.yellow, + ], + ), + ); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + DrawingBoard( + shapes: app.shapes, + onClick: app.addShape, + ), + ToolPanel( + tools: app.tools, + ), + ], + ); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/mixin/icon_box_mixin.dart b/patterns/abstract_factory/tool_panel_factory/mixin/icon_box_mixin.dart new file mode 100644 index 0000000..31eaa00 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/mixin/icon_box_mixin.dart @@ -0,0 +1,71 @@ +import 'dart:async'; +import 'dart:typed_data'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +import '../pattern/tool_factory.dart'; +import '../pattern/shape.dart'; + +mixin IconBoxMixin implements FactoryTool { + Image? _icon; + + @override + Image get icon => _icon!; + + Future updateIcon(Color color) async { + final shape = createShape(0, 0, color); + final pngBytes = await _pngImageFromShape(shape); + _icon = Image.memory( + pngBytes, + fit: BoxFit.none, + ); + } + + Future _pngImageFromShape(Shape shape) async { + final iconSize = 32.0; + + final rec = PictureRecorder(); + final can = Canvas( + rec, + Rect.fromLTWH(0, 0, iconSize, iconSize), + ); + + _scaleTo(can, shape, iconSize); + shape.paint(can); + + final image = await rec.endRecording().toImage( + iconSize.toInt(), + iconSize.toInt(), + ); + final bytes = await image.toByteData(format: ImageByteFormat.png); + + if (bytes == null) { + throw 'Bytes is empty.'; + } + + return bytes.buffer.asUint8List(); + } + + void _scaleTo(Canvas can, Shape shape, double iconSize) { + var xMove = 0.0; + var yMove = 0.0; + late double w; + late double h; + + if (shape.width >= shape.height) { + yMove = (shape.width - shape.height); + w = iconSize / shape.width; + h = iconSize / (shape.height + yMove); + yMove /= 2; + } else { + xMove = (shape.height - shape.width); + w = iconSize / (shape.width + xMove); + h = iconSize / shape.height; + xMove /= 2; + } + + can.scale(w, h); + can.translate(xMove, yMove); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/pattern/shape.dart b/patterns/abstract_factory/tool_panel_factory/pattern/shape.dart new file mode 100644 index 0000000..37330c9 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/pattern/shape.dart @@ -0,0 +1,30 @@ +import 'dart:ui'; + +abstract class Shape { + double get x => _x; + + double get y => _y; + + final Color color; + + Shape({ + required double x, + required double y, + required this.color, + }) : _x = x, + _y = y; + + void paint(Canvas can); + + double get width; + + double get height; + + void centerToFit() { + _x -= width / 2; + _y -= height / 2; + } + + double _x; + double _y; +} diff --git a/patterns/abstract_factory/tool_panel_factory/pattern/tool_factory.dart b/patterns/abstract_factory/tool_panel_factory/pattern/tool_factory.dart new file mode 100644 index 0000000..0a02001 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/pattern/tool_factory.dart @@ -0,0 +1,9 @@ +import 'package:flutter/widgets.dart'; + +import 'shape.dart'; + +abstract class FactoryTool { + Shape createShape(double x, double y, Color color); + + Image get icon; +} diff --git a/patterns/abstract_factory/tool_panel_factory/shapes/circle_shape.dart b/patterns/abstract_factory/tool_panel_factory/shapes/circle_shape.dart new file mode 100644 index 0000000..f6c21b2 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/shapes/circle_shape.dart @@ -0,0 +1,36 @@ +import 'dart:ui'; + +import 'package:flutter/cupertino.dart'; + +import '../pattern/shape.dart'; + +class CircleShape extends Shape { + final double radius; + + CircleShape({ + required this.radius, + required double x, + required double y, + required Color color, + }) : super( + x: x, + y: y, + color: color, + ); + + @override + void paint(Canvas can) { + final pos = width / 2; + can.drawCircle( + Offset(pos, pos), + radius, + Paint()..color = color, + ); + } + + @override + double get width => radius * 2; + + @override + double get height => width; +} diff --git a/patterns/abstract_factory/tool_panel_factory/shapes/line_shape.dart b/patterns/abstract_factory/tool_panel_factory/shapes/line_shape.dart new file mode 100644 index 0000000..0e08df5 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/shapes/line_shape.dart @@ -0,0 +1,37 @@ +import 'dart:ui'; + +import 'package:flutter/cupertino.dart'; + +import '../pattern/shape.dart'; + +class LineShape extends Shape { + final double length; + + LineShape({ + required this.length, + required double x, + required double y, + required Color color, + }) : super( + x: x, + y: y, + color: color, + ); + + @override + void paint(Canvas can) { + can.drawLine( + Offset(0, length), + Offset(length, 0), + Paint() + ..color = color + ..strokeWidth = 3, + ); + } + + @override + double get width => length; + + @override + double get height => length; +} diff --git a/patterns/abstract_factory/tool_panel_factory/shapes/star_shape.dart b/patterns/abstract_factory/tool_panel_factory/shapes/star_shape.dart new file mode 100644 index 0000000..6c68798 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/shapes/star_shape.dart @@ -0,0 +1,52 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +import '../pattern/shape.dart'; + +class StarShape extends Shape { + final double radius; + + StarShape({ + required this.radius, + required double x, + required double y, + required Color color, + }) : super( + x: x, + y: y, + color: color, + ); + + @override + void paint(Canvas can) { + final path = Path()..addPolygon(_createStar(), true); + can.drawPath(path, Paint()..color = color); + } + + @override + double get width => radius * 2; + + @override + double get height => radius * 2; + + List _createStar() { + const alpha = (2 * pi) / 10; + + final starXY = radius; + + final points = []; + + for (var i = 11; i != 0; i--) { + var r = radius * (i % 2 + 1) / 2; + var omega = alpha * i; + points.add(Offset( + (r * sin(omega)) + starXY, + (r * cos(omega)) + starXY, + )); + } + + return points; + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/shapes/triangle_shape.dart b/patterns/abstract_factory/tool_panel_factory/shapes/triangle_shape.dart new file mode 100644 index 0000000..1d7e373 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/shapes/triangle_shape.dart @@ -0,0 +1,49 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/cupertino.dart'; + +import '../pattern/shape.dart'; + +class TriangleShape extends Shape { + final double sideLength; + + TriangleShape({ + required this.sideLength, + required double x, + required double y, + required Color color, + }) : super( + x: x, + y: y, + color: color, + ); + + @override + void paint(Canvas can) { + final path = Path() + ..addPolygon( + [ + Offset(0, height), + Offset(width / 2, 0), + Offset(width, height), + ], + true, + ); + + can.drawPath( + path, + Paint() + ..strokeJoin = StrokeJoin.round + ..style = PaintingStyle.stroke + ..color = color + ..strokeWidth = 5, + ); + } + + @override + double get width => sideLength; + + @override + double get height => sideLength * sqrt(3) / 2; +} diff --git a/patterns/abstract_factory/tool_panel_factory/shapes/txt_shape.dart b/patterns/abstract_factory/tool_panel_factory/shapes/txt_shape.dart new file mode 100644 index 0000000..3eaafbc --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/shapes/txt_shape.dart @@ -0,0 +1,51 @@ +import 'dart:ui'; + +import '../pattern/shape.dart'; + +class Txt extends Shape { + final String text; + final double fontSize; + + Txt({ + required this.text, + required this.fontSize, + required double x, + required double y, + required Color color, + }) : super( + x: x, + y: y, + color: color, + ) { + _initTextParagraph(); + } + + @override + void paint(Canvas can) { + can.drawParagraph(_paragraph, Offset.zero); + } + + @override + double get width => _paragraph.maxIntrinsicWidth; + + @override + double get height => _paragraph.height; + + late final Paragraph _paragraph; + + void _initTextParagraph() { + final style = ParagraphStyle( + textDirection: TextDirection.ltr, + ); + final tStyle = TextStyle( + fontFamily: 'Arial', + color: color, + fontSize: fontSize, + ); + _paragraph = (ParagraphBuilder(style) + ..pushStyle(tStyle) + ..addText(text)) + .build(); + _paragraph.layout(ParagraphConstraints(width: double.infinity)); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/widgets/colors_tool_bar.dart b/patterns/abstract_factory/tool_panel_factory/widgets/colors_tool_bar.dart new file mode 100644 index 0000000..9b30eac --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/widgets/colors_tool_bar.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +import '../app/tools.dart'; +import 'independent/tool_bar.dart'; +import 'independent/tool_button.dart'; + +class ColorsToolBar extends StatelessWidget { + final Tools tools; + + const ColorsToolBar({Key? key, required this.tools}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ToolBar( + title: 'colors', + child: ValueListenableBuilder( + valueListenable: tools.activeColor, + builder: (_, activeColor, __) { + return Column( + children: [ + for (final color in tools.colors) + ToolButton( + icon: Icon(Icons.circle, color: color), + active: color == activeColor, + onTap: () { + tools.activeColor.value = color; + }, + ), + ], + ); + }, + ), + ); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/widgets/drawing_board.dart b/patterns/abstract_factory/tool_panel_factory/widgets/drawing_board.dart new file mode 100644 index 0000000..3735982 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/widgets/drawing_board.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import '../app/shapes.dart'; +import 'independent/event_listenable_builder.dart'; +import 'shape_widget.dart'; + +class DrawingBoard extends StatelessWidget { + final Shapes shapes; + final Function(double x, double y) onClick; + + const DrawingBoard({ + Key? key, + required this.shapes, + required this.onClick, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTapDown: (e) => addShape(e.localPosition), + child: Container( + color: Color(0xff1f1f1f), + child: EventListenableBuilder( + event: shapes.onAddShapeEvent, + builder: (_) { + return Stack( + children: [ + for (final shape in shapes) ShapeWidget(shape: shape), + ], + ); + }, + ), + ), + ); + } + + void addShape(Offset position) { + onClick(position.dx, position.dy); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/widgets/factories_tool_bar.dart b/patterns/abstract_factory/tool_panel_factory/widgets/factories_tool_bar.dart new file mode 100644 index 0000000..51514f0 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/widgets/factories_tool_bar.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; + +import '../app/tools.dart'; +import 'independent/tool_bar.dart'; +import 'independent/tool_button.dart'; + +class FactoriesToolBar extends StatelessWidget { + final Tools tools; + + const FactoriesToolBar({Key? key, required this.tools}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ToolBar( + title: 'factories', + child: FutureBuilder( + future: tools.iconsReady, + builder: (_, snapshot) { + return snapshot.hasData + ? _buildToolButtons() + : Padding( + padding: EdgeInsets.all(10), + child: CircularProgressIndicator()); + }, + ), + ); + } + + Widget _buildToolButtons() { + return ValueListenableBuilder( + valueListenable: tools.activeFactory, + builder: (_, activeFactory, __) { + return Column( + children: [ + for (final factory in tools.factories) + ToolButton( + icon: factory.icon, + active: factory == activeFactory, + onTap: () => tools.activeFactory.value = factory, + ), + ], + ); + }, + ); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/widgets/independent/event_listenable_builder.dart b/patterns/abstract_factory/tool_panel_factory/widgets/independent/event_listenable_builder.dart new file mode 100644 index 0000000..c1d02d2 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/widgets/independent/event_listenable_builder.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +class EventListenableBuilder extends StatefulWidget { + final T event; + final Widget Function(BuildContext context) builder; + + const EventListenableBuilder({ + Key? key, + required this.event, + required this.builder, + }) : super(key: key); + + @override + _EventListenableBuilderState createState() => + _EventListenableBuilderState(event); +} + +class _EventListenableBuilderState + extends State> { + final T event; + + _EventListenableBuilderState(this.event); + + @override + void initState() { + event.addListener(_update); + super.initState(); + } + + @override + void dispose() { + event.removeListener(_update); + super.dispose(); + } + + void _update() { + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return widget.builder(context); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/widgets/independent/hove.dart b/patterns/abstract_factory/tool_panel_factory/widgets/independent/hove.dart new file mode 100644 index 0000000..b3bb973 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/widgets/independent/hove.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; + +class Hover extends StatefulWidget { + final Function(double hoverForce) builder; + + const Hover({Key? key, required this.builder}) : super(key: key); + + @override + _HoverState createState() => _HoverState(); +} + +class _HoverState extends State with SingleTickerProviderStateMixin { + late final AnimationController _animation; + + @override + void initState() { + _animation = AnimationController( + duration: Duration(milliseconds: 200), + value: 0.0, + vsync: this, + )..addListener( + () { + setState(() {}); + }, + ); + super.initState(); + } + + @override + void dispose() { + _animation.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) { + _animation.forward(from: _animation.value); + }, + onExit: (_) { + _animation.reverse(); + }, + child: widget.builder(_animation.value), + ); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/widgets/independent/tool_bar.dart b/patterns/abstract_factory/tool_panel_factory/widgets/independent/tool_bar.dart new file mode 100644 index 0000000..b0aa11a --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/widgets/independent/tool_bar.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; + +class ToolBar extends StatelessWidget { + final String title; + final Widget child; + + const ToolBar({ + Key? key, + required this.title, + required this.child, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return _buildNamedPanel( + title: title, + child: child, + ); + } + + Widget _buildNamedPanel({required String title, required Widget child}) { + return Container( + width: 64, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + color: Color(0xFF3B3B3B), + borderRadius: BorderRadius.all(Radius.circular(3)), + boxShadow: [ + BoxShadow( + color: Color(0x7C000000), + blurRadius: 6, + offset: Offset(2, 2), + ), + ]), + child: Column( + children: [ + _buildTitle(title), + child, + ], + ), + ); + } + + Widget _buildTitle(String title) { + return Container( + height: 20, + color: Colors.white10, + width: double.infinity, + alignment: Alignment.center, + child: Text( + title, + style: TextStyle( + color: Colors.white70, + fontSize: 13, + fontFamily: 'Arial', + decoration: TextDecoration.none, + fontWeight: FontWeight.normal, + ), + ), + ); + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/widgets/independent/tool_button.dart b/patterns/abstract_factory/tool_panel_factory/widgets/independent/tool_button.dart new file mode 100644 index 0000000..c0a633b --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/widgets/independent/tool_button.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +import 'hove.dart'; + +class ToolButton extends StatelessWidget { + final Function() onTap; + final bool active; + final Widget icon; + + const ToolButton({ + Key? key, + required this.onTap, + required this.active, + required this.icon, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Hover( + builder: (hoverForce) { + return Container( + width: 64, + height: 64, + color: color(hoverForce), + child: Padding( + padding: EdgeInsets.all(16), + child: icon, + ), + ); + }, + ), + ); + } + + Color color(double hoverForce) => active + ? Color.lerp(Colors.white10, Colors.white12, hoverForce)! + : Color.lerp(Colors.transparent, Colors.white10, hoverForce)!; +} diff --git a/patterns/abstract_factory/tool_panel_factory/widgets/shape_widget.dart b/patterns/abstract_factory/tool_panel_factory/widgets/shape_widget.dart new file mode 100644 index 0000000..aec0755 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/widgets/shape_widget.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import '../pattern/shape.dart'; + +class ShapeWidget extends StatelessWidget { + final Shape shape; + + const ShapeWidget({ + Key? key, + required this.shape, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Positioned( + left: shape.x, + top: shape.y, + child: CustomPaint( + size: Size(shape.width, shape.height), + painter: PaintShape(shape), + ), + ); + } +} + +class PaintShape extends CustomPainter { + final Shape shape; + + PaintShape(this.shape); + + @override + void paint(Canvas canvas, Size _) { + shape.paint(canvas); + } + + @override + bool shouldRepaint(covariant CustomPainter _) { + return false; + } +} diff --git a/patterns/abstract_factory/tool_panel_factory/widgets/tool_panel.dart b/patterns/abstract_factory/tool_panel_factory/widgets/tool_panel.dart new file mode 100644 index 0000000..6e0729d --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/widgets/tool_panel.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +import '../app/tools.dart'; +import 'colors_tool_bar.dart'; +import 'factories_tool_bar.dart'; + +class ToolPanel extends StatelessWidget { + final Tools tools; + const ToolPanel({Key? key, required this.tools}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Positioned( + left: 12, + top: 12, + child: Column( + children: [ + FactoriesToolBar(tools: tools), + SizedBox(height: 24), + ColorsToolBar(tools: tools), + ], + ), + ); + } +} From 8c334184d73ee01940d4f97779779eed2a351489 Mon Sep 17 00:00:00 2001 From: ilopX Date: Mon, 16 May 2022 22:58:25 +0300 Subject: [PATCH 2/5] Impl project "Tool Panel". --- bin/main.dart | 2 +- .../abstract_factory/tool_panel_factory/app/tools.dart | 4 ++-- .../tool_panel_factory/factories/circle_factory.dart | 2 +- .../tool_panel_factory/factories/line_factory.dart | 2 +- .../tool_panel_factory/factories/star_factory.dart | 2 +- .../tool_panel_factory/factories/triangle_factory.dart | 2 +- .../tool_panel_factory/factories/txt_factory.dart | 4 ++-- patterns/abstract_factory/tool_panel_factory/main.dart | 8 ++++---- .../tool_panel_factory/mixin/icon_box_mixin.dart | 2 +- .../tool_panel_factory/pattern/tool_factory.dart | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/bin/main.dart b/bin/main.dart index 1f3c009..5c35ea6 100644 --- a/bin/main.dart +++ b/bin/main.dart @@ -19,7 +19,7 @@ class MyApp extends StatelessWidget { '/observer/subscriber_flutter_widget': (_) => SubscriberFlutterApp(), '/adapter/flutter_adapter': (_) => FlutterAdapterApp(), '/memento/flutter_memento_editor': (_) => FlutterMementoEditorApp(), - '/abstract_factory/tool_panel': (_) => WToolPanelApp(), + '/abstract_factory/tool_panel_factory': (_) => ToolPanelFactoryApp(), }, ); } diff --git a/patterns/abstract_factory/tool_panel_factory/app/tools.dart b/patterns/abstract_factory/tool_panel_factory/app/tools.dart index c0cf9c7..2e7c5bc 100644 --- a/patterns/abstract_factory/tool_panel_factory/app/tools.dart +++ b/patterns/abstract_factory/tool_panel_factory/app/tools.dart @@ -7,10 +7,10 @@ import '../mixin/icon_box_mixin.dart'; import '../pattern/tool_factory.dart'; class Tools { - final List factories; + final List factories; final List colors; - final activeFactory = ValueNotifier(null); + final activeFactory = ValueNotifier(null); final activeColor = ValueNotifier(Color(0x0FFFFFFFF)); diff --git a/patterns/abstract_factory/tool_panel_factory/factories/circle_factory.dart b/patterns/abstract_factory/tool_panel_factory/factories/circle_factory.dart index 529e311..b924f83 100644 --- a/patterns/abstract_factory/tool_panel_factory/factories/circle_factory.dart +++ b/patterns/abstract_factory/tool_panel_factory/factories/circle_factory.dart @@ -5,7 +5,7 @@ import '../pattern/tool_factory.dart'; import '../shapes/circle_shape.dart'; import '../pattern/shape.dart'; -class CircleFactory extends FactoryTool with IconBoxMixin { +class CircleFactory extends ToolFactory with IconBoxMixin { @override Shape createShape(double x, double y, Color color) { return CircleShape( diff --git a/patterns/abstract_factory/tool_panel_factory/factories/line_factory.dart b/patterns/abstract_factory/tool_panel_factory/factories/line_factory.dart index aa7e01f..6ed1bf1 100644 --- a/patterns/abstract_factory/tool_panel_factory/factories/line_factory.dart +++ b/patterns/abstract_factory/tool_panel_factory/factories/line_factory.dart @@ -5,7 +5,7 @@ import '../pattern/tool_factory.dart'; import '../pattern/shape.dart'; import '../shapes/line_shape.dart'; -class LineFactory extends FactoryTool with IconBoxMixin { +class LineFactory extends ToolFactory with IconBoxMixin { @override Shape createShape(double x, double y, Color color) { return LineShape( diff --git a/patterns/abstract_factory/tool_panel_factory/factories/star_factory.dart b/patterns/abstract_factory/tool_panel_factory/factories/star_factory.dart index 794bcdd..a7d2737 100644 --- a/patterns/abstract_factory/tool_panel_factory/factories/star_factory.dart +++ b/patterns/abstract_factory/tool_panel_factory/factories/star_factory.dart @@ -5,7 +5,7 @@ import '../pattern/tool_factory.dart'; import '../pattern/shape.dart'; import '../shapes/star_shape.dart'; -class StarFactory extends FactoryTool with IconBoxMixin { +class StarFactory extends ToolFactory with IconBoxMixin { @override Shape createShape(double x, double y, Color color) { return StarShape( diff --git a/patterns/abstract_factory/tool_panel_factory/factories/triangle_factory.dart b/patterns/abstract_factory/tool_panel_factory/factories/triangle_factory.dart index 1cac49c..08951a8 100644 --- a/patterns/abstract_factory/tool_panel_factory/factories/triangle_factory.dart +++ b/patterns/abstract_factory/tool_panel_factory/factories/triangle_factory.dart @@ -5,7 +5,7 @@ import '../pattern/tool_factory.dart'; import '../pattern/shape.dart'; import '../shapes/triangle_shape.dart'; -class TriangleFactory extends FactoryTool with IconBoxMixin { +class TriangleFactory extends ToolFactory with IconBoxMixin { @override Shape createShape(double x, double y, Color color) { return TriangleShape( diff --git a/patterns/abstract_factory/tool_panel_factory/factories/txt_factory.dart b/patterns/abstract_factory/tool_panel_factory/factories/txt_factory.dart index 011b093..fac6ad8 100644 --- a/patterns/abstract_factory/tool_panel_factory/factories/txt_factory.dart +++ b/patterns/abstract_factory/tool_panel_factory/factories/txt_factory.dart @@ -5,12 +5,12 @@ import '../pattern/tool_factory.dart'; import '../shapes/txt_shape.dart'; import '../pattern/shape.dart'; -class TxtFactory extends FactoryTool with IconBoxMixin { +class TxtFactory extends ToolFactory with IconBoxMixin { @override Shape createShape(double x, double y, Color color) { return Txt( text: 'Text', - fontSize: 100, + fontSize: 50, x: x, y: y, color: color, diff --git a/patterns/abstract_factory/tool_panel_factory/main.dart b/patterns/abstract_factory/tool_panel_factory/main.dart index f20c48b..fef07c5 100644 --- a/patterns/abstract_factory/tool_panel_factory/main.dart +++ b/patterns/abstract_factory/tool_panel_factory/main.dart @@ -11,14 +11,14 @@ import 'factories/txt_factory.dart'; import 'widgets/drawing_board.dart'; import 'widgets/tool_panel.dart'; -class WToolPanelApp extends StatefulWidget { - const WToolPanelApp({Key? key}) : super(key: key); +class ToolPanelFactoryApp extends StatefulWidget { + const ToolPanelFactoryApp({Key? key}) : super(key: key); @override - _WToolPanelAppState createState() => _WToolPanelAppState(); + _ToolPanelFactoryAppState createState() => _ToolPanelFactoryAppState(); } -class _WToolPanelAppState extends State { +class _ToolPanelFactoryAppState extends State { final app = App( shapes: Shapes([]), tools: Tools( diff --git a/patterns/abstract_factory/tool_panel_factory/mixin/icon_box_mixin.dart b/patterns/abstract_factory/tool_panel_factory/mixin/icon_box_mixin.dart index 31eaa00..11016a1 100644 --- a/patterns/abstract_factory/tool_panel_factory/mixin/icon_box_mixin.dart +++ b/patterns/abstract_factory/tool_panel_factory/mixin/icon_box_mixin.dart @@ -7,7 +7,7 @@ import 'package:flutter/material.dart'; import '../pattern/tool_factory.dart'; import '../pattern/shape.dart'; -mixin IconBoxMixin implements FactoryTool { +mixin IconBoxMixin implements ToolFactory { Image? _icon; @override diff --git a/patterns/abstract_factory/tool_panel_factory/pattern/tool_factory.dart b/patterns/abstract_factory/tool_panel_factory/pattern/tool_factory.dart index 0a02001..a556a98 100644 --- a/patterns/abstract_factory/tool_panel_factory/pattern/tool_factory.dart +++ b/patterns/abstract_factory/tool_panel_factory/pattern/tool_factory.dart @@ -2,7 +2,7 @@ import 'package:flutter/widgets.dart'; import 'shape.dart'; -abstract class FactoryTool { +abstract class ToolFactory { Shape createShape(double x, double y, Color color); Image get icon; From 09a1aa084a0324de260570d2e374473aacf98fc1 Mon Sep 17 00:00:00 2001 From: ilopX Date: Mon, 16 May 2022 22:58:41 +0300 Subject: [PATCH 3/5] Add README. --- .../tool_panel_factory/README.md | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 patterns/abstract_factory/tool_panel_factory/README.md diff --git a/patterns/abstract_factory/tool_panel_factory/README.md b/patterns/abstract_factory/tool_panel_factory/README.md new file mode 100644 index 0000000..36e82d0 --- /dev/null +++ b/patterns/abstract_factory/tool_panel_factory/README.md @@ -0,0 +1,49 @@ +# Abstract Factory Pattern +Abstract Factory is a creational design pattern that lets you produce families of related objects +without specifying their concrete classes. + +Tutorial: [here](https://refactoring.guru/design-patterns/abstract-factory). + +### Online demo: +Click on the picture to see the [demo](https://RefactoringGuru.github.io/design-patterns-dart/#/abstract_factory/tool_panel_factory). + +[![image](https://user-images.githubusercontent.com/8049534/168668992-369a1bab-9f97-4333-a20e-ffd06bf91b54.png)](https://refactoringguru.github.io/design-patterns-dart/#/abstract_factory/tool_panel_factory) + +### Diagram: +![image](https://user-images.githubusercontent.com/8049534/168672053-73ae1c9c-8fad-45ae-9247-429f7b5da565.png) + +### Client code (using the "createShape" method): +```dart +final app = App( + tools: Tools( + factories: [ + TxtFactory(), + LineFactory(), + CircleFactory(), + TriangleFactory(), + StarFactory(), + ], + ), +); + +class App { + void addShape(double x, double y) { + final newShape = activeFactory.createShape(x, y, activeColor); + shapes.add(newShape); + } +} + +mixin IconBoxMixin implements FactoryTool { + Image? _icon; + + @override + Image get icon => _icon!; + + Future updateIcon(Color color) async { + final shape = createShape(0, 0, color); + final pngBytes = await _pngImageFromShape(shape); + _icon = Image.memory(pngBytes); + } +} +``` + From 18b1898b09576fc34f688384100d9206cc473b93 Mon Sep 17 00:00:00 2001 From: ilopX Date: Mon, 16 May 2022 23:01:28 +0300 Subject: [PATCH 4/5] Update main README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 69a5674..5c6a719 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ It contains **Dart** examples for all classic **GoF** design patterns. # Implementation checklist: - [ ] **Creation** - - [x] **Abstract Factory** [[Conceptual Gui Factory](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/abstract_factory/conceptual_gui_factory)] + - [x] **Abstract Factory** [[Conceptual Gui Factory](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/abstract_factory/conceptual_gui_factory)] [[Tool Panel Factory](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/abstract_factory/tool_panel_factory)] - [x] **Builder** - [[Color Text Format](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/builder/color_text_format)] - [x] **Factory Method** [[Conceptual Platform Dialog](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/factory_method/conceptual_platform_dialog)] - [x] **Prototype** - [[Shapes](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/prototype/shapes)] From 8859d33e91cf9ce60823946e0ce8b5994296ace0 Mon Sep 17 00:00:00 2001 From: ilopX Date: Mon, 16 May 2022 23:01:51 +0300 Subject: [PATCH 5/5] Bump version 0.23.0. --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16286c9..cacd0cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.23.0 +- Add "Tool Panel Factory" flutter example + ## 0.22.0 - Add visitor pattern: "Shape Xml Export". diff --git a/pubspec.yaml b/pubspec.yaml index 6ac14f2..95bed6c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: design_patterns_dart description: Dart examples for all classic GoF design patterns. -version: 0.22.0 +version: 0.23.0 homepage: https://refactoring.guru/design-patterns repository: https://github.com/RefactoringGuru/design-patterns-dart issue_tracker: https://github.com/RefactoringGuru/design-patterns-dart/issue