diff --git a/documentation/img/box.png b/documentation/img/box.png index 2983507..5c24a03 100644 Binary files a/documentation/img/box.png and b/documentation/img/box.png differ diff --git a/documentation/img/circle.png b/documentation/img/circle.png index afea52a..99e4b9c 100644 Binary files a/documentation/img/circle.png and b/documentation/img/circle.png differ diff --git a/documentation/img/highlight.png b/documentation/img/highlight.png index 20595fc..2730374 100644 Binary files a/documentation/img/highlight.png and b/documentation/img/highlight.png differ diff --git a/documentation/img/overview.png b/documentation/img/overview.png deleted file mode 100644 index eed9f17..0000000 Binary files a/documentation/img/overview.png and /dev/null differ diff --git a/documentation/img/underline.png b/documentation/img/underline.png index 3a7ab69..2ce3f40 100644 Binary files a/documentation/img/underline.png and b/documentation/img/underline.png differ diff --git a/example/lib/screens/examples/box_example_screen.dart b/example/lib/screens/examples/box_example_screen.dart index 2dbf4df..b015d95 100644 --- a/example/lib/screens/examples/box_example_screen.dart +++ b/example/lib/screens/examples/box_example_screen.dart @@ -56,7 +56,7 @@ class BoxExampleScreen extends StatelessWidget { TextDecorator.boxed( style: BoxStyle.curled, text: const Text( - 'Curcled Box', + 'Curled Box', style: TextStyle(fontSize: 16), ), strokeWidth: 2, diff --git a/example/lib/screens/examples/circle_example_screen.dart b/example/lib/screens/examples/circle_example_screen.dart index 115cf24..b66f37a 100644 --- a/example/lib/screens/examples/circle_example_screen.dart +++ b/example/lib/screens/examples/circle_example_screen.dart @@ -23,6 +23,18 @@ class CircleExampleScreen extends StatelessWidget { ), ), const SizedBox(height: 32), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 30.0), + child: TextDecorator.circled( + style: CircleStyle.basic, + text: const Text( + 'Franz jagt im komplett verwahrlosten Taxi quer durch Berlin', + style: TextStyle(fontSize: 16), + textAlign: TextAlign.center, + ), + ), + ), + const SizedBox(height: 64), TextDecorator.circled( text: const Text( 'Circled Text', @@ -30,11 +42,15 @@ class CircleExampleScreen extends StatelessWidget { ), ), const SizedBox(height: 32), - TextDecorator.circled( - style: CircleStyle.basic, - text: const Text( - 'Franz jagt im komplett verwahrlosten Taxi quer durch Berlin', - style: TextStyle(fontSize: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 30.0), + child: TextDecorator.circled( + style: CircleStyle.circled, + text: const Text( + 'Franz jagt im komplett verwahrlosten Taxi quer durch Berlin', + style: TextStyle(fontSize: 16), + textAlign: TextAlign.center, + ), ), ), ], diff --git a/lib/src/modules/box/painter/open_circle_painter.dart b/lib/src/modules/box/painter/open_circle_painter.dart deleted file mode 100644 index 6cb51a1..0000000 --- a/lib/src/modules/box/painter/open_circle_painter.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'dart:math'; -import 'package:flutter/material.dart'; -import 'package:flutter_text_decorator/src/modules/base/decoration_base.dart'; -import 'package:flutter_text_decorator/src/modules/base/text_decoration_painter.dart'; -import 'package:flutter_text_decorator/src/modules/circle/mixins/circle_mixin.dart'; - -/// A [CustomPainter] that draws an "open circle" decoration around text. -/// -/// This painter renders two distinct arcs, one above and one below the text, -/// creating the visual effect of an incomplete or stylized circle. It extends -/// [TextDecoratorPainter], taking text content, style, and a [DecorationBase] -/// object (which provides color and stroke width) to define its appearance. -/// -/// The sizing and positioning of the arcs are calculated based on the dimensions -/// of the text (obtained via the [CircleConstraints] mixin using `getCircleSizes`) -/// and internal scaling factors and offsets to achieve the specific "open circle" look. -/// These scaling factors and angles are currently hardcoded to produce a specific -/// aesthetic. -/// -/// This painter asserts that the provided [text] is not empty and that -/// `decoration.strokeWidth` is greater than 0. -/// -/// Example (conceptual, as direct usage might be part of a larger framework): -/// ```dart -/// CustomPaint( -/// painter: OpenCirclePainter( -/// text: "Open Circle Text", -/// textStyle: TextStyle(fontSize: 16, color: Colors.black), -/// decoration: CircleDecoration(color: Colors.blue, strokeWidth: 2.0), -/// ), -/// child: Text( -/// "Open Circle Text", -/// style: TextStyle(fontSize: 16, color: Colors.black), -/// ), -/// ) -/// ``` - -class OpenCirclePainter extends TextDecoratorPainter with CircleConstraints { - OpenCirclePainter({ - required super.text, - required super.textStyle, - required super.decoration, - }) : assert(text != '' && decoration.strokeWidth > 0, 'text should not be empty and decoration.strokeWidth should be greater than 0'); - - @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = decoration.color - ..strokeWidth = decoration.strokeWidth - ..style = PaintingStyle.stroke; - - final circleSize = getCircleSizes(text: text, textStyle: textStyle); - - final scaledHorizontalRadius = circleSize.horizontalRadius * 2.1; - final scaledVerticalRadiusBottomCircle = circleSize.verticalRadius * 2.9; - final scaledVerticalRadiusTopCircle = circleSize.verticalRadius * 3.5; - const verticalOffset = 1.8; - - const startAngleBottomArc = -1.5; - const sweepAngleBottomArc = pi + 1.5; - const startAngleTopArc = pi + 6.2; - const sweepAngleTopArc = pi - 1; - - final centerOffset = Offset(size.width / 2, (size.height / verticalOffset) + verticalOffset); - - canvas - ..drawArc( - Rect.fromCenter( - center: centerOffset, - width: scaledHorizontalRadius, - height: scaledVerticalRadiusBottomCircle, - ), - startAngleBottomArc, - sweepAngleBottomArc, - false, - paint, - ) - ..drawArc( - Rect.fromCenter( - center: centerOffset, - width: scaledHorizontalRadius, - height: scaledVerticalRadiusTopCircle, - ), - startAngleTopArc, - sweepAngleTopArc, - false, - paint, - ); - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return false; - } -} diff --git a/lib/src/modules/circle/classes/circle_angle_option.dart b/lib/src/modules/circle/classes/circle_angle_option.dart new file mode 100644 index 0000000..af7cddf --- /dev/null +++ b/lib/src/modules/circle/classes/circle_angle_option.dart @@ -0,0 +1,47 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'dart:math'; + +import 'package:flutter_text_decorator/src/modules/circle/painter/open_circle_painter.dart'; + +/// Defines a set of angles for rendering an "open circle" decoration. +/// +/// This class encapsulates the start and sweep angles for two arcs +/// (typically one for the bottom part of the circle and one for the top part) +/// that together form an open or stylized circle. +/// +/// It provides `const` factories for predefined angle configurations, +/// such as `bottomLeft()` or `topLeft()`, which can be used with +/// [OpenCirclePainter] to specify the orientation of the circle's opening. +/// Instances of this class are immutable. +class CircleAngleOption { + const CircleAngleOption({ + required this.startAngleBottomCircle, + required this.sweepAngleBottomCircle, + required this.startAngleTopCircle, + required this.sweepAngleTopCircle, + }); + + const factory CircleAngleOption.bottomLeft() = _BottomLeftCircleAngleOption; + + /// The starting angle for the bottom arc, in radians. + final double startAngleBottomCircle; + + /// The sweep angle for the bottom arc, in radians. + final double sweepAngleBottomCircle; + + /// The starting angle for the top arc, in radians. + final double startAngleTopCircle; + + /// The sweep angle for the top arc, in radians. + final double sweepAngleTopCircle; +} + +class _BottomLeftCircleAngleOption extends CircleAngleOption { + const _BottomLeftCircleAngleOption() + : super( + startAngleBottomCircle: -1.5 + pi, + sweepAngleBottomCircle: pi + 1.5, + startAngleTopCircle: (pi + 6.2) + pi, + sweepAngleTopCircle: pi - 1, + ); +} diff --git a/lib/src/modules/circle/mixins/circle_mixin.dart b/lib/src/modules/circle/mixins/circle_mixin.dart index eca53df..de3a5cf 100644 --- a/lib/src/modules/circle/mixins/circle_mixin.dart +++ b/lib/src/modules/circle/mixins/circle_mixin.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_text_decorator/src/modules/circle/classes/circle_size.dart'; /// A mixin that provides a utility method to calculate dimensions @@ -19,12 +20,13 @@ mixin CircleConstraints { CircleSize getCircleSizes({ required String text, required TextStyle textStyle, + required Size size, }) { final textSpan = TextSpan(text: text, style: textStyle); final textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, - )..layout(); + )..layout(maxWidth: size.width); const textHeightOffset = 2; const textWidthScale = 1.8; diff --git a/lib/src/modules/circle/painter/closed_circle_painter.dart b/lib/src/modules/circle/painter/closed_circle_painter.dart index ae7bb46..b38929f 100644 --- a/lib/src/modules/circle/painter/closed_circle_painter.dart +++ b/lib/src/modules/circle/painter/closed_circle_painter.dart @@ -43,11 +43,10 @@ class ClosedCirclePainter extends TextDecoratorPainter with CircleConstraints { ..strokeWidth = decoration.strokeWidth ..style = PaintingStyle.stroke; - final circleSize = getCircleSizes(text: text, textStyle: textStyle); + final circleSize = getCircleSizes(text: text, textStyle: textStyle, size: size); - final scaledHorizontalRadius = circleSize.horizontalRadius * 2.1; - final scaledVerticalRadiusBottomCircle = circleSize.verticalRadius * 2.9; - const verticalOffset = 1.8; + final scaledVerticalRadiusBottomCircle = circleSize.verticalRadius * 3.5; + const verticalOffset = 2.5; final centerOffset = Offset( size.width / 2, @@ -57,7 +56,7 @@ class ClosedCirclePainter extends TextDecoratorPainter with CircleConstraints { canvas.drawOval( Rect.fromCenter( center: centerOffset, - width: scaledHorizontalRadius, + width: size.width * 1.1, height: scaledVerticalRadiusBottomCircle, ), paint, diff --git a/lib/src/modules/circle/painter/open_circle_painter.dart b/lib/src/modules/circle/painter/open_circle_painter.dart index ab2f9f7..86516fa 100644 --- a/lib/src/modules/circle/painter/open_circle_painter.dart +++ b/lib/src/modules/circle/painter/open_circle_painter.dart @@ -1,7 +1,7 @@ -import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_text_decorator/src/modules/base/decoration_base.dart'; import 'package:flutter_text_decorator/src/modules/base/text_decoration_painter.dart'; +import 'package:flutter_text_decorator/src/modules/circle/classes/circle_angle_option.dart'; import 'package:flutter_text_decorator/src/modules/circle/mixins/circle_mixin.dart'; /// A [CustomPainter] that draws an "open circle" decoration around text. @@ -35,8 +35,14 @@ class OpenCirclePainter extends TextDecoratorPainter with CircleConstraints { required super.text, required super.textStyle, required super.decoration, + this.circleAngleOption = const CircleAngleOption.bottomLeft(), }) : assert(text != '' && decoration.strokeWidth > 0, 'text should not be empty and decoration.strokeWidth should be greater than 0'); + /// Defines the start and sweep angles for the two arcs forming the open circle. + /// + /// Defaults to [CircleAngleOption.bottomLeft] if not specified. + final CircleAngleOption circleAngleOption; + @override void paint(Canvas canvas, Size size) { final paint = Paint() @@ -44,16 +50,12 @@ class OpenCirclePainter extends TextDecoratorPainter with CircleConstraints { ..strokeWidth = decoration.strokeWidth ..style = PaintingStyle.stroke; - final circleSize = getCircleSizes(text: text, textStyle: textStyle); + final circleSize = getCircleSizes(text: text, textStyle: textStyle, size: size); - final scaledHorizontalRadius = circleSize.horizontalRadius * 2.1; - final scaledVerticalRadiusBottomCircle = circleSize.verticalRadius * 2.9; + final scaledHorizontalRadius = circleSize.horizontalRadius * 2; + final scaledVerticalRadiusBottomCircle = circleSize.verticalRadius * 3; final scaledVerticalRadiusTopCircle = circleSize.verticalRadius * 3.5; const verticalOffset = 1.8; - const startAngleBottomCircle = -1.5; - const sweepAngleBottomCircle = pi + 1.5; - const startAngleTopCircle = pi + 6.2; - const sweepAngleTopCircle = pi - 1; final centerOffset = Offset( size.width / 2, @@ -67,8 +69,8 @@ class OpenCirclePainter extends TextDecoratorPainter with CircleConstraints { width: scaledHorizontalRadius, height: scaledVerticalRadiusBottomCircle, ), - startAngleBottomCircle, - sweepAngleBottomCircle, + circleAngleOption.startAngleBottomCircle, + circleAngleOption.sweepAngleBottomCircle, false, paint, ) @@ -78,8 +80,8 @@ class OpenCirclePainter extends TextDecoratorPainter with CircleConstraints { width: scaledHorizontalRadius, height: scaledVerticalRadiusTopCircle, ), - startAngleTopCircle, - sweepAngleTopCircle, + circleAngleOption.startAngleTopCircle, + circleAngleOption.sweepAngleTopCircle, false, paint, ); @@ -87,6 +89,12 @@ class OpenCirclePainter extends TextDecoratorPainter with CircleConstraints { @override bool shouldRepaint(covariant CustomPainter oldDelegate) { - return false; + if (oldDelegate is OpenCirclePainter) { + return oldDelegate.circleAngleOption.startAngleBottomCircle != circleAngleOption.startAngleBottomCircle || + oldDelegate.circleAngleOption.sweepAngleBottomCircle != circleAngleOption.sweepAngleBottomCircle || + oldDelegate.circleAngleOption.startAngleTopCircle != circleAngleOption.startAngleTopCircle || + oldDelegate.circleAngleOption.sweepAngleTopCircle != circleAngleOption.sweepAngleTopCircle; + } + return true; // Repaint if the delegate type changes or for other unknown reasons } }