diff --git a/example/lib/edge_detection_shape/animated_touch_bubble_part.dart b/example/lib/edge_detection_shape/animated_touch_bubble_part.dart index a587aaf..4af7942 100644 --- a/example/lib/edge_detection_shape/animated_touch_bubble_part.dart +++ b/example/lib/edge_detection_shape/animated_touch_bubble_part.dart @@ -23,18 +23,18 @@ class _AnimatedTouchBubblePartState extends State with ); _sizeAnimation = Tween( - begin: 0.5, - end: 1.0 + begin: 0.5, + end: 1.0 ).animate(_controller); _colorAnimation = ColorTween( - begin: Theme.of(context).accentColor.withOpacity(0.5), - end: Theme.of(context).accentColor.withOpacity(0.0) + begin: Theme.of(context).accentColor.withOpacity(0.5), + end: Theme.of(context).accentColor.withOpacity(0.0) ).animate( - CurvedAnimation( - parent: _controller, - curve: Interval(0.5, 1.0) - ) + CurvedAnimation( + parent: _controller, + curve: Interval(0.5, 1.0) + ) ); _controller.repeat(); @@ -53,31 +53,31 @@ class _AnimatedTouchBubblePartState extends State with children: [ Center( child: Container( - width: widget.dragging ? widget.size : widget.size / 2, - height: widget.dragging ? widget.size : widget.size / 2, + width: widget.dragging ? 0 : widget.size / 2, + height: widget.dragging ? 0 : widget.size / 2, decoration: BoxDecoration( - color: Theme.of(context).accentColor.withOpacity(0.5), - borderRadius: widget.dragging ? BorderRadius.circular(widget.size) : BorderRadius.circular(widget.size / 4) + color: Theme.of(context).accentColor.withOpacity(0.5), + borderRadius: widget.dragging ? BorderRadius.circular(widget.size) : BorderRadius.circular(widget.size / 4) ) ) ), AnimatedBuilder( - builder: (BuildContext context, Widget child) { - return Center( - child: Container( - width: widget.dragging ? 0 : widget.size * _sizeAnimation.value, - height: widget.dragging ? 0 : widget.size * _sizeAnimation.value, - decoration: BoxDecoration( - border: Border.all( - color: _colorAnimation.value, - width: widget.size / 20 - ), - borderRadius: widget.dragging ? BorderRadius.zero : BorderRadius.circular(widget.size * _sizeAnimation.value / 2) - ) + builder: (BuildContext context, Widget child) { + return Center( + child: Container( + width: widget.dragging ? 0 : widget.size * _sizeAnimation.value, + height: widget.dragging ? 0 : widget.size * _sizeAnimation.value, + decoration: BoxDecoration( + border: Border.all( + color: _colorAnimation.value, + width: widget.size / 20 + ), + borderRadius: widget.dragging ? BorderRadius.zero : BorderRadius.circular(widget.size * _sizeAnimation.value / 2) ) - ); - }, - animation: _controller + ) + ); + }, + animation: _controller ) ], ); diff --git a/example/lib/edge_detection_shape/edge_detection_shape.dart b/example/lib/edge_detection_shape/edge_detection_shape.dart index c48e3ef..06cc2e4 100644 --- a/example/lib/edge_detection_shape/edge_detection_shape.dart +++ b/example/lib/edge_detection_shape/edge_detection_shape.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:simple_edge_detection/edge_detection.dart'; import 'edge_painter.dart'; +import 'magnifier.dart'; import 'touch_bubble.dart'; class EdgeDetectionShape extends StatefulWidget { @@ -33,10 +34,12 @@ class _EdgeDetectionShapeState extends State { double top; double left; + Offset currentDragPosition; + @override void didChangeDependencies() { double shortestSide = min(MediaQuery.of(context).size.width, MediaQuery.of(context).size.height); - edgeDraggerSize = shortestSide / 8; + edgeDraggerSize = shortestSide / 12; super.didChangeDependencies(); } @@ -50,19 +53,23 @@ class _EdgeDetectionShapeState extends State { @override Widget build(BuildContext context) { - return Stack( - children: [ - _getTouchBubbles(), - CustomPaint( - painter: EdgePainter( - points: points, - color: Theme.of(context).accentColor.withOpacity(0.5) - ), - ) - ], + return Magnifier( + visible: currentDragPosition != null, + position: currentDragPosition, + child: Stack( + children: [ + _getTouchBubbles(), + CustomPaint( + painter: EdgePainter( + points: points, + color: Theme.of(context).accentColor.withOpacity(0.5) + ) + ) + ], + ) ); } - + void _calculateDimensionValues() { top = 0.0; left = 0.0; @@ -85,17 +92,43 @@ class _EdgeDetectionShapeState extends State { ); } + Offset _clampOffset(Offset givenOffset) { + double absoluteX = givenOffset.dx * renderedImageWidth; + double absoluteY = givenOffset.dy * renderedImageHeight; + + return Offset( + absoluteX.clamp(0.0, renderedImageWidth) / renderedImageWidth, + absoluteY.clamp(0.0, renderedImageHeight) / renderedImageHeight + ); + } + Widget _getTouchBubbles() { points = [ - Offset(left + edgeDetectionResult.topLeft.dx * renderedImageWidth, top + edgeDetectionResult.topLeft.dy * renderedImageHeight), - Offset(left + edgeDetectionResult.topRight.dx * renderedImageWidth, top + edgeDetectionResult.topRight.dy * renderedImageHeight), - Offset(left + edgeDetectionResult.bottomRight.dx * renderedImageWidth, top + (edgeDetectionResult.bottomRight.dy * renderedImageHeight)), - Offset(left + edgeDetectionResult.bottomLeft.dx * renderedImageWidth, top + edgeDetectionResult.bottomLeft.dy * renderedImageHeight), - Offset(left + edgeDetectionResult.topLeft.dx * renderedImageWidth, top + edgeDetectionResult.topLeft.dy * renderedImageHeight), + Offset( + left + edgeDetectionResult.topLeft.dx * renderedImageWidth, + top + edgeDetectionResult.topLeft.dy * renderedImageHeight + ), + Offset( + left + edgeDetectionResult.topRight.dx * renderedImageWidth, + top + edgeDetectionResult.topRight.dy * renderedImageHeight + ), + Offset( + left + edgeDetectionResult.bottomRight.dx * renderedImageWidth, + top + (edgeDetectionResult.bottomRight.dy * renderedImageHeight) + ), + Offset( + left + edgeDetectionResult.bottomLeft.dx * renderedImageWidth, + top + edgeDetectionResult.bottomLeft.dy * renderedImageHeight + ), + Offset( + left + edgeDetectionResult.topLeft.dx * renderedImageWidth, + top + edgeDetectionResult.topLeft.dy * renderedImageHeight + ), ]; final Function onDragFinished = () { - setState(() {}); + currentDragPosition = null; + setState(() {}); }; return Container( @@ -108,8 +141,12 @@ class _EdgeDetectionShapeState extends State { size: edgeDraggerSize, onDrag: (position) { setState(() { - edgeDetectionResult.topLeft += _getNewPositionAfterDrag( - position, renderedImageWidth, renderedImageHeight + currentDragPosition = Offset(points[0].dx, points[0].dy); + Offset newTopLeft = _getNewPositionAfterDrag( + position, renderedImageWidth, renderedImageHeight + ); + edgeDetectionResult.topLeft = _clampOffset( + edgeDetectionResult.topLeft + newTopLeft ); }); }, @@ -123,9 +160,13 @@ class _EdgeDetectionShapeState extends State { size: edgeDraggerSize, onDrag: (position) { setState(() { - edgeDetectionResult.topRight += _getNewPositionAfterDrag( - position, renderedImageWidth, renderedImageHeight + Offset newTopRight = _getNewPositionAfterDrag( + position, renderedImageWidth, renderedImageHeight + ); + edgeDetectionResult.topRight = _clampOffset( + edgeDetectionResult.topRight + newTopRight ); + currentDragPosition = Offset(points[1].dx, points[1].dy); }); }, onDragFinished: onDragFinished @@ -135,30 +176,38 @@ class _EdgeDetectionShapeState extends State { ), Positioned( child: TouchBubble( - size: edgeDraggerSize, - onDrag: (position) { - setState(() { - edgeDetectionResult.bottomRight += _getNewPositionAfterDrag( + size: edgeDraggerSize, + onDrag: (position) { + setState(() { + Offset newBottomRight = _getNewPositionAfterDrag( position, renderedImageWidth, renderedImageHeight - ); - }); - }, - onDragFinished: onDragFinished + ); + edgeDetectionResult.bottomRight = _clampOffset( + edgeDetectionResult.bottomRight + newBottomRight + ); + currentDragPosition = Offset(points[2].dx, points[2].dy); + }); + }, + onDragFinished: onDragFinished ), left: points[2].dx - (edgeDraggerSize / 2), top: points[2].dy - (edgeDraggerSize / 2) ), Positioned( child: TouchBubble( - size: edgeDraggerSize, - onDrag: (position) { - setState(() { - edgeDetectionResult.bottomLeft += _getNewPositionAfterDrag( - position, renderedImageWidth, renderedImageHeight - ); - }); - }, - onDragFinished: onDragFinished + size: edgeDraggerSize, + onDrag: (position) { + setState(() { + Offset newBottomLeft = _getNewPositionAfterDrag( + position, renderedImageWidth, renderedImageHeight + ); + edgeDetectionResult.bottomLeft = _clampOffset( + edgeDetectionResult.bottomLeft + newBottomLeft + ); + currentDragPosition = Offset(points[3].dx, points[3].dy); + }); + }, + onDragFinished: onDragFinished ), left: points[3].dx - (edgeDraggerSize / 2), top: points[3].dy - (edgeDraggerSize / 2) diff --git a/example/lib/edge_detection_shape/magnifier.dart b/example/lib/edge_detection_shape/magnifier.dart new file mode 100644 index 0000000..c25d688 --- /dev/null +++ b/example/lib/edge_detection_shape/magnifier.dart @@ -0,0 +1,134 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; + +class Magnifier extends StatefulWidget { + const Magnifier({ + @required this.child, + @required this.position, + this.visible = true, + this.scale = 2.0, + this.alignment = Alignment.topLeft, + this.size = const Size(160, 160), + Key key + }) : super(key: key); + + final Widget child; + final Offset position; + final bool visible; + final double scale; + final Alignment alignment; + final Size size; + + @override + _MagnifierState createState() => _MagnifierState(); +} + +class _MagnifierState extends State { + Size _magnifierSize; + double _scale; + Matrix4 _matrix; + + @override + void initState() { + _magnifierSize = widget.size; + _scale = widget.scale; + _calculateMatrix(); + + super.initState(); + } + + @override + void didUpdateWidget(Magnifier oldWidget) { + super.didUpdateWidget(oldWidget); + + if (!widget.visible) { + return; + } + + if (oldWidget.size != widget.size) { + _magnifierSize = widget.size; + } + + if (oldWidget.scale != widget.scale) { + _scale = widget.scale; + _matrix = Matrix4.identity()..scale(_scale); + } + + _calculateMatrix(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + widget.child, + if (widget.visible) + _getMagnifier(context) + ], + ); + } + + void _calculateMatrix() { + if (widget.position == null) { + return; + } + + setState(() { + double newX = widget.position.dx - (_magnifierSize.width / 2 / _scale); + double newY = widget.position.dy - (_magnifierSize.height / 2 / _scale); + + final Matrix4 updatedMatrix = Matrix4.identity() + ..scale(_scale, _scale) + ..translate(-newX, -newY); + + _matrix = updatedMatrix; + }); + } + + Widget _getMagnifier(BuildContext context) { + return Align( + alignment: widget.alignment, + child: ClipOval( + child: BackdropFilter( + filter: ImageFilter.matrix(_matrix.storage), + child: CustomPaint( + painter: MagnifierPainter( + color: Theme.of(context).accentColor + ), + size: _magnifierSize, + ), + ), + ), + ); + } +} + +class MagnifierPainter extends CustomPainter { + const MagnifierPainter({ + @required this.color, + this.strokeWidth = 5 + }); + + final double strokeWidth; + final Color color; + + @override + void paint(Canvas canvas, Size size) { + Paint paintObject = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = strokeWidth + ..color = color; + + canvas.drawCircle( + size.center( + Offset(0, 0) + ), + size.longestSide / 2, paintObject + ); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return true; + } +} \ No newline at end of file diff --git a/example/lib/edge_detection_shape/touch_bubble.dart b/example/lib/edge_detection_shape/touch_bubble.dart index 2d45933..eb6c5d2 100644 --- a/example/lib/edge_detection_shape/touch_bubble.dart +++ b/example/lib/edge_detection_shape/touch_bubble.dart @@ -23,33 +23,31 @@ class _TouchBubbleState extends State { @override Widget build(BuildContext context) { return GestureDetector( - behavior: HitTestBehavior.opaque, - onTapDown: (_) => _startDragging(), - onPanStart: (_) => _startDragging(), - onTapUp: (_) => _cancelDragging(), - onTapCancel: _cancelDragging, - onPanUpdate: _drag, - onPanCancel: _cancelDragging, - onPanEnd: (_) => _cancelDragging(), - child: Container( - width: widget.size, - height: widget.size, - decoration: BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular(widget.size / 2) - ), - child: AnimatedTouchBubblePart( - dragging: dragging, - size: widget.size, - ) + behavior: HitTestBehavior.opaque, + onPanStart: _startDragging, + onPanUpdate: _drag, + onPanCancel: _cancelDragging, + onPanEnd: (_) => _cancelDragging(), + child: Container( + width: widget.size, + height: widget.size, + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(widget.size / 2) + ), + child: AnimatedTouchBubblePart( + dragging: dragging, + size: widget.size, ) + ) ); } - void _startDragging() { + void _startDragging(DragStartDetails data) { setState(() { dragging = true; }); + widget.onDrag(data.localPosition - Offset(widget.size / 2, widget.size / 2)); } void _cancelDragging() { @@ -63,6 +61,7 @@ class _TouchBubbleState extends State { if (!dragging) { return; } + widget.onDrag(data.delta); } } \ No newline at end of file