diff --git a/CHANGELOG.md b/CHANGELOG.md index b3143b3..cddc47d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ - Added `HStack` widget. - Added `VStack` widget. - Added `Rectangle` widget. +- Added `Ellipse` widget. +- Added `Shadow` widget. ## 0.0.1 - Sept, 2023 Setting up the project structure. This release is not usable. diff --git a/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery.xcodeproj/project.pbxproj b/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery.xcodeproj/project.pbxproj index 24b1cf3..d65a1b2 100644 --- a/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery.xcodeproj/project.pbxproj +++ b/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 714951452C0EB31C00818B06 /* ShapesPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714951442C0EB31C00818B06 /* ShapesPage.swift */; }; 714951472C0EB52400818B06 /* RectangleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714951462C0EB52400818B06 /* RectangleExamples.swift */; }; + 717523012C1D330600FB7CF3 /* ShadowExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 717523002C1D330500FB7CF3 /* ShadowExamples.swift */; }; 7179CF832B26DEB900C5927B /* TextTypographyPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179CF822B26DEB900C5927B /* TextTypographyPage.swift */; }; 7179CF852B26DEDD00C5927B /* TextAccessibilityPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179CF842B26DEDD00C5927B /* TextAccessibilityPage.swift */; }; 7179CF872B26DF0E00C5927B /* TextLocalizationPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179CF862B26DF0E00C5927B /* TextLocalizationPage.swift */; }; @@ -56,6 +57,7 @@ /* Begin PBXFileReference section */ 714951442C0EB31C00818B06 /* ShapesPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapesPage.swift; sourceTree = ""; }; 714951462C0EB52400818B06 /* RectangleExamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectangleExamples.swift; sourceTree = ""; }; + 717523002C1D330500FB7CF3 /* ShadowExamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowExamples.swift; sourceTree = ""; }; 7179CF822B26DEB900C5927B /* TextTypographyPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextTypographyPage.swift; sourceTree = ""; }; 7179CF842B26DEDD00C5927B /* TextAccessibilityPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextAccessibilityPage.swift; sourceTree = ""; }; 7179CF862B26DF0E00C5927B /* TextLocalizationPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLocalizationPage.swift; sourceTree = ""; }; @@ -117,6 +119,7 @@ 714951442C0EB31C00818B06 /* ShapesPage.swift */, 714951462C0EB52400818B06 /* RectangleExamples.swift */, 71C346EE2C1A7B580087A514 /* EllipseExamples.swift */, + 717523002C1D330500FB7CF3 /* ShadowExamples.swift */, ); path = shapes; sourceTree = ""; @@ -441,6 +444,7 @@ C9CAE2B32AE1066B0042DBC7 /* MotionPage.swift in Sources */, B3DD96702B66513800F66E9F /* ImageLocalizationPage.swift in Sources */, C9FB17562C0C4AB4004479AA /* HStackExamples.swift in Sources */, + 717523012C1D330600FB7CF3 /* ShadowExamples.swift in Sources */, C9CAE2812AC23AB40042DBC7 /* swift_ui_galleryApp.swift in Sources */, C902DDE52AE38C8A00242DBA /* VStackExamples.swift in Sources */, C9CAE2AF2AE106540042DBC7 /* ScaffoldsPage.swift in Sources */, diff --git a/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery/shapes/EllipseExamples.swift b/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery/shapes/EllipseExamples.swift index a4df90e..a787681 100644 --- a/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery/shapes/EllipseExamples.swift +++ b/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery/shapes/EllipseExamples.swift @@ -23,6 +23,7 @@ struct EllipsePage: View { Ellipse() .stroke(LinearGradient(gradient: Gradient(colors: [.yellow, .blue]), startPoint: .leading, endPoint: .trailing), lineWidth: 14) .frame(width: 200, height: 100) + }.frame(width: 300) } } diff --git a/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery/shapes/RectangleExamples.swift b/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery/shapes/RectangleExamples.swift index baf49cb..6827c0a 100644 --- a/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery/shapes/RectangleExamples.swift +++ b/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery/shapes/RectangleExamples.swift @@ -23,7 +23,8 @@ struct RectanglePage: View { Rectangle() .stroke(LinearGradient(gradient: Gradient(colors: [.yellow, .blue]), startPoint: .leading, endPoint: .trailing), lineWidth: 14) .frame(width: 200, height: 100) - } + + }.frame(width: 300) } } } diff --git a/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery/shapes/ShadowExamples.swift b/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery/shapes/ShadowExamples.swift new file mode 100644 index 0000000..8f9c86a --- /dev/null +++ b/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery/shapes/ShadowExamples.swift @@ -0,0 +1,70 @@ +import SwiftUI + +struct ShadowPage: View { + var body: some View { + ScrollView { + VStack(spacing: 40) { + + Rectangle() + .fill(Color.purple) + .shadow(color: .gray, radius: 10, x: 10, y: 10) + .frame(width: 200, height: 100) + + Ellipse() + .fill(Color.yellow) + .shadow(color: .gray, radius: 10, x: 10, y: 10) + .frame(width: 200, height: 100) + + Star(points: 5) + .stroke(Color.blue, lineWidth: 5) + .frame(width: 200, height: 200) + .shadow(color: .gray, radius: 10, x: 10, y: 10) + + Image("dash_hello") + .resizable() + .aspectRatio(contentMode: .fit) + .shadow(color: .gray, radius: 10, x: 10, y: 10) + .frame(width: 200, height: 200) + + }.frame(width: 300) + } + } +} + +struct Star: Shape { + let points: Int + + func path(in rect: CGRect) -> Path { + var path = Path() + + let starExtrusion: CGFloat = 0.5 + let angle = .pi / Double(points) + let center = CGPoint(x: rect.width / 2, y: rect.height / 2) + let radius = min(rect.width, rect.height) / 2 + + for i in 0..<2 * points { + let rotation = Double(i) * angle + let position = CGPoint( + x: center.x + CGFloat(cos(rotation) * radius * (i % 2 == 0 ? 1 : starExtrusion)), + y: center.y + CGFloat(sin(rotation) * radius * (i % 2 == 0 ? 1 : starExtrusion)) + ) + + if i == 0 { + path.move(to: position) + } else { + path.addLine(to: position) + } + } + + path.closeSubpath() + + return path + } +} + +struct ShadowPage_Previews: PreviewProvider { + static var previews: some View { + ShadowPage() + } +} + diff --git a/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery/shapes/ShapesPage.swift b/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery/shapes/ShapesPage.swift index 2387126..f6115f9 100644 --- a/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery/shapes/ShapesPage.swift +++ b/doc_swift_ui_gallery/swift_ui_gallery/swift_ui_gallery/shapes/ShapesPage.swift @@ -4,17 +4,34 @@ struct ShapesPage: View { var body: some View { NavigationStack { List() { - NavigationLink { - RectanglePage() - } label: { - Text("Rectangle") + Section() { + NavigationLink { + RectanglePage() + } label: { + Text("Rectangle") + } + + NavigationLink { + EllipsePage() + } label: { + Text("Ellipse") + } + + } header: { + Text("Shapes") } - NavigationLink { - EllipsePage() - } label: { - Text("Ellipse") + Section() { + NavigationLink { + ShadowPage() + } label: { + Text("shadow") + } + } header: { + Text("View methods") } + + }.navigationTitle("Shapes") } } diff --git a/example/assets/images/dash_hello/dash_ciao.png b/example/assets/images/dash_hello/dash_ciao.png new file mode 100644 index 0000000..6b202a2 Binary files /dev/null and b/example/assets/images/dash_hello/dash_ciao.png differ diff --git a/example/assets/images/dash_hello/dash_hello.png b/example/assets/images/dash_hello/dash_hello.png new file mode 100644 index 0000000..fca166a Binary files /dev/null and b/example/assets/images/dash_hello/dash_hello.png differ diff --git a/example/assets/images/dash_hello/dash_hello_default.png b/example/assets/images/dash_hello/dash_hello_default.png new file mode 100644 index 0000000..540b444 Binary files /dev/null and b/example/assets/images/dash_hello/dash_hello_default.png differ diff --git a/example/lib/main.dart b/example/lib/main.dart index 1937f36..87240a4 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -34,7 +34,6 @@ class NavigationViewState extends State { height: 54, color: const Color(0xFFFFFF00), child: Center( - // child: LateBoundBuilder( child: LateBoundBuild( builder: (context) { final title = NavigationView.of(context).title ?? "DEFAULT TILE"; diff --git a/example/lib/shapes/ellipse.dart b/example/lib/shapes/ellipse.dart index 9440043..7df3306 100644 --- a/example/lib/shapes/ellipse.dart +++ b/example/lib/shapes/ellipse.dart @@ -14,7 +14,7 @@ class EllipseDemo extends StatelessWidget { Frame( width: 200, height: 100, - child: Ellipse( + Ellipse( fillColor: Colors.blue, ), ), @@ -23,7 +23,7 @@ class EllipseDemo extends StatelessWidget { Frame( width: 200, height: 100, - child: Ellipse( + Ellipse( fillGradient: LinearGradient( colors: [Colors.yellow, Colors.orange], ), @@ -34,7 +34,7 @@ class EllipseDemo extends StatelessWidget { Frame( width: 200, height: 100, - child: Ellipse( + Ellipse( strokeColor: Colors.red, strokeLineWidth: 4.0, ), @@ -44,7 +44,7 @@ class EllipseDemo extends StatelessWidget { Frame( width: 200, height: 100, - child: Ellipse( + Ellipse( strokeGradient: LinearGradient( colors: [Colors.yellow, Colors.blue], ), diff --git a/example/lib/shapes/rectangle.dart b/example/lib/shapes/rectangle.dart index fb3ab8c..2e004d3 100644 --- a/example/lib/shapes/rectangle.dart +++ b/example/lib/shapes/rectangle.dart @@ -14,7 +14,7 @@ class RectangleDemo extends StatelessWidget { Frame( width: 200, height: 100, - child: Rectangle( + Rectangle( fillColor: Colors.blue, ), ), @@ -23,7 +23,7 @@ class RectangleDemo extends StatelessWidget { Frame( width: 200, height: 100, - child: Rectangle( + Rectangle( fillGradient: LinearGradient( colors: [Colors.yellow, Colors.orange], ), @@ -34,7 +34,7 @@ class RectangleDemo extends StatelessWidget { Frame( width: 200, height: 100, - child: Rectangle( + Rectangle( strokeColor: Colors.red, strokeLineWidth: 4.0, ), @@ -44,7 +44,7 @@ class RectangleDemo extends StatelessWidget { Frame( width: 200, height: 100, - child: Rectangle( + Rectangle( strokeGradient: LinearGradient( colors: [Colors.yellow, Colors.blue], ), @@ -52,6 +52,7 @@ class RectangleDemo extends StatelessWidget { ), ), ], + spacing: 20, ); } } diff --git a/example/lib/shapes/shadow.dart b/example/lib/shapes/shadow.dart new file mode 100644 index 0000000..ca5bbe6 --- /dev/null +++ b/example/lib/shapes/shadow.dart @@ -0,0 +1,165 @@ +import 'dart:math'; + +import 'package:flutter/widgets.dart'; +import 'package:swift_ui/swift_ui.dart'; + +class ShadowDemo extends StatelessWidget { + const ShadowDemo({ + super.key, + }); + + static const color = Color(0xFF8E8E93); // `.grey` in SwiftUI + static const radius = 10.0; + static const x = 10.0; + static const y = 10.0; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.white, + child: VStack( + [ + // Rectangle with shadow + const Frame( + width: 200, + height: 100, + Shadow( + color: color, + radius: radius, + x: x, + y: y, + Rectangle( + fillColor: Colors.purple, + ), + ), + ), + + // Ellipse with shadow + const Frame( + width: 200, + height: 100, + Shadow( + color: color, + radius: radius, + x: x, + y: y, + Ellipse( + fillColor: Colors.yellow, + ), + ), + ), + + // Star with shadow + const Frame( + width: 200, + height: 200, + Shadow( + color: color, + radius: radius, + x: x, + y: y, + Star( + points: 5, + strokeLineWidth: 5, + strokeColor: Colors.blue, + ), + ), + ), + + // Image with alpha and shadow + Frame( + width: 200, + height: 200, + Shadow( + color: color, + radius: radius, + x: x, + y: y, + Image.asset( + 'assets/images/dash_hello/dash_hello.png', + fit: BoxFit.contain, + ), + ), + ), + ], + spacing: 20, + ), + ); + } +} + +class Star extends StatelessWidget { + final int points; + final double strokeLineWidth; + final Color strokeColor; + + const Star({ + super.key, + required this.points, + required this.strokeLineWidth, + required this.strokeColor, + }); + + @override + Widget build(BuildContext context) { + return CustomPaint( + size: Size.infinite, + painter: _StarPainter( + points: points, + strokeLineWidth: strokeLineWidth, + strokeColor: strokeColor, + ), + ); + } +} + +class _StarPainter extends CustomPainter { + final int points; + final double strokeLineWidth; + final Color strokeColor; + + _StarPainter({ + required this.points, + required this.strokeLineWidth, + required this.strokeColor, + }); + + @override + void paint(Canvas canvas, Size size) { + final path = Path(); + + const starExtrusion = 0.5; + final angle = pi / points; + final center = Offset(size.width / 2, size.height / 2); + final radius = min(size.width, size.height) / 2; + + for (int i = 0; i < 2 * points; i++) { + final rotation = i * angle; + final position = Offset( + center.dx + cos(rotation) * radius * (i % 2 == 0 ? 1 : starExtrusion), + center.dy + sin(rotation) * radius * (i % 2 == 0 ? 1 : starExtrusion), + ); + + if (i == 0) { + path.moveTo(position.dx, position.dy); + } else { + path.lineTo(position.dx, position.dy); + } + } + + path.close(); + + final paint = Paint() + ..style = PaintingStyle.stroke + ..color = strokeColor + ..strokeWidth = strokeLineWidth; + + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(_StarPainter oldDelegate) => + oldDelegate.points != points || + oldDelegate.strokeLineWidth != strokeLineWidth || + oldDelegate.strokeColor != strokeColor; +} diff --git a/example/lib/shapes/shapes_examples.dart b/example/lib/shapes/shapes_examples.dart index e907fa7..d87f353 100644 --- a/example/lib/shapes/shapes_examples.dart +++ b/example/lib/shapes/shapes_examples.dart @@ -4,6 +4,7 @@ import 'package:example/shapes/rectangle.dart'; import 'package:flutter/cupertino.dart'; import 'ellipse.dart'; +import 'shadow.dart'; class ShapesPage extends StatelessWidget { const ShapesPage({super.key}); @@ -14,6 +15,7 @@ class ShapesPage extends StatelessWidget { title: "Shapes", groups: [ InventoryGroup( + title: "SHAPES", items: [ InventoryItem( label: "Rectangle", @@ -25,6 +27,15 @@ class ShapesPage extends StatelessWidget { ), ], ), + InventoryGroup( + title: "MODIFIERS", + items: [ + InventoryItem( + label: "Shadow", + pageBuilder: createDemo(const ShadowDemo()), + ), + ], + ), ], ); } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index fa63001..b86effe 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -22,39 +22,6 @@ dev_dependencies: sdk: flutter flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages + assets: + - assets/images/dash_hello/dash_hello.png \ No newline at end of file diff --git a/lib/src/drawing_and_graphics/border.dart b/lib/src/drawing_and_graphics/border.dart index 0cf1755..f94e303 100644 --- a/lib/src/drawing_and_graphics/border.dart +++ b/lib/src/drawing_and_graphics/border.dart @@ -2,10 +2,10 @@ import 'package:flutter/widgets.dart' as widgets; class Border extends widgets.StatelessWidget { const Border( - this.color, { + this.child, { + required this.color, super.key, this.width = 1, - required this.child, }); final widgets.Color color; diff --git a/lib/src/drawing_and_graphics/shadow.dart b/lib/src/drawing_and_graphics/shadow.dart new file mode 100644 index 0000000..b9d5eca --- /dev/null +++ b/lib/src/drawing_and_graphics/shadow.dart @@ -0,0 +1,167 @@ +import 'dart:ui'; + +import 'package:flutter/widgets.dart'; + +/// A widget that adds a shadow effect to its child widget. +/// +/// Non-rectangular child shapes or images that include alpha regions are +/// supported. The shadow will follow the visible portions of the child widget +/// rather than the rectangular size of the widget. +/// +/// In addition to supplying a child widget, you must also specify the radius of +/// the shadow, which is a measure of how fuzzy the edge of the shadow should be. +class Shadow extends StatelessWidget { + const Shadow( + this.child, { + super.key, + // In SwiftUI the default is `Color(.sRGBLinear, white: 0, opacity: 0.33)` + this.color = const Color(0x54000000), + required this.radius, + this.x = 0, + this.y = 0, + }); + + /// The color of the shadow. + final Color color; + + /// The blur radius of the shadow. + /// + /// `0.0` is sharp while higher values are more fuzzy. + final double radius; + + /// The horizontal offset of the shadow. + final double x; + + /// The vertical offset of the shadow. + final double y; + + /// The widget to which the shadow effect will be applied. + final Widget child; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Transform.translate( + offset: Offset(x, y), + child: _Blur( + sigmaX: radius, + sigmaY: radius, + _Tint( + color: color, + _Desaturate( + child, + // The Desaturate widget removes all transparency from the + // whatever color the child widget is so that the shadow + // opacity is independent of the widget opacity. + // However, this also removes the effects of antialiasing, which + // makes the border look jagged if there is no blur radius. So + // we retain the alpha for that edge case. + retainAlpha: radius == 0.0, + ), + ), + ), + ), + child + ], + ); + } +} + +/// A widget that applies a blur filter to its child widget. +class _Blur extends StatelessWidget { + const _Blur( + this.child, { + this.sigmaX = 5.0, + this.sigmaY = 5.0, + }); + + /// The widget to which the blur filter will be applied. + final Widget child; + + /// The horizontal blur radius. + /// + /// The default is 5.0. + final double sigmaX; + + /// The vertical blur radius. + /// + /// The default is 5.0. + final double sigmaY; + + @override + Widget build(BuildContext context) { + return ImageFiltered( + imageFilter: ImageFilter.blur( + sigmaX: sigmaX, + sigmaY: sigmaY, + tileMode: TileMode.decal, + ), + child: child, + ); + } +} + +/// A widget that applies a color filter to its child widget. +/// +/// You must specify a color. +class _Tint extends StatelessWidget { + const _Tint( + this.child, { + required this.color, + }); + + /// The widget to which the color filter will be applied. + final Widget child; + + /// The color to which the child will be tinted. + final Color color; + + @override + Widget build(BuildContext context) { + return ColorFiltered( + colorFilter: ColorFilter.mode( + color, + BlendMode.modulate, + ), + child: child, + ); + } +} + +/// A widget that removes the RGB saturation of its child widget. +class _Desaturate extends StatelessWidget { + const _Desaturate( + this.child, { + required this.retainAlpha, + }); + + final Widget child; + + /// Whether to retain the alpha channel of the child widget when desaturating. + final bool retainAlpha; + + @override + Widget build(BuildContext context) { + final ColorFilter filter; + if (retainAlpha) { + filter = const ColorFilter.matrix([ + 1, 1, 1, 0, 0, // red -> white + 1, 1, 1, 0, 0, // green -> white + 1, 1, 1, 0, 0, // blue -> white + 0, 0, 0, 1, 0, // retain alpha + ]); + } else { + filter = const ColorFilter.matrix([ + 1, 1, 1, 0, 0, // + 1, 1, 1, 0, 0, // + 1, 1, 1, 0, 0, // + 0, 0, 0, 255, -254, // remove alpha unless it's 0 + ]); + } + return ColorFiltered( + colorFilter: filter, + child: child, + ); + } +} diff --git a/lib/src/layout/frame.dart b/lib/src/layout/frame.dart index ec360cb..2f53aac 100644 --- a/lib/src/layout/frame.dart +++ b/lib/src/layout/frame.dart @@ -4,12 +4,12 @@ import 'alignment.dart'; /// A layout widget that aligns its child within a fixed-size rectangle. class Frame extends StatelessWidget { - const Frame({ + const Frame( + this.child, { super.key, this.width, this.height, this.alignment = Alignment.center, - required this.child, }); /// The width of the frame. diff --git a/lib/src/shapes/rectangle.dart b/lib/src/shapes/rectangle.dart index f66a6c6..c55860a 100644 --- a/lib/src/shapes/rectangle.dart +++ b/lib/src/shapes/rectangle.dart @@ -23,22 +23,19 @@ class Rectangle extends StatelessWidget { /// A color used for painting the outline of the rectangle. /// - /// The stroke line is drawn inside the frame of the rectangle, and its - /// width is determined by [strokeLineWidth]. - /// /// This is an alternative to [strokeGradient]. final Color? strokeColor; /// A gradient used for painting the outline of the rectangle. /// - /// The stroke line is drawn inside the frame of the rectangle, and its - /// width is determined by [strokeLineWidth]. - /// /// This is an alternative to [strokeColor]. final Gradient? strokeGradient; /// The width of the stroke used to paint the outline of the rectangle. /// + /// The stroke line is centered along the edge of the rectangle. Half of + /// [strokeLineWidth] is painted outside of the rectangle and half inside. + /// /// The default is 1.0. final double strokeLineWidth; @@ -111,7 +108,7 @@ class _RectanglePainter extends CustomPainter { } void _paintSolidStroke(Canvas canvas, Size size) { - final rect = _adjustRectForLineWidth(size); + final rect = Rect.fromLTWH(0, 0, size.width, size.height); final paint = Paint() ..style = PaintingStyle.stroke ..color = strokeColor! @@ -119,17 +116,8 @@ class _RectanglePainter extends CustomPainter { canvas.drawRect(rect, paint); } - Rect _adjustRectForLineWidth(Size size) { - return Rect.fromLTWH( - strokeLineWidth / 2, - strokeLineWidth / 2, - size.width - strokeLineWidth, - size.height - strokeLineWidth, - ); - } - void _paintGradientStroke(Canvas canvas, Size size) { - final rect = _adjustRectForLineWidth(size); + final rect = Rect.fromLTWH(0, 0, size.width, size.height); final paint = Paint() ..style = PaintingStyle.stroke ..shader = strokeGradient!.createShader(rect) diff --git a/lib/swift_ui.dart b/lib/swift_ui.dart index 3ef7c55..eff6a35 100644 --- a/lib/swift_ui.dart +++ b/lib/swift_ui.dart @@ -5,6 +5,7 @@ export 'src/controls_and_indicators/button.dart'; // Drawing and Graphics export 'src/drawing_and_graphics/border.dart'; +export 'src/drawing_and_graphics/shadow.dart'; // Layout export 'src/layout/grid.dart'; diff --git a/test/layout/frame_golden_test.dart b/test/layout/frame_golden_test.dart index c0ba54b..a8d473c 100644 --- a/test/layout/frame_golden_test.dart +++ b/test/layout/frame_golden_test.dart @@ -15,7 +15,7 @@ void main() { width: 100, height: 100, alignment: Alignment.center, - child: Container( + Container( width: 10, height: 10, color: Colors.blue, @@ -31,7 +31,7 @@ void main() { width: 100, height: 100, alignment: Alignment.top, - child: Container( + Container( width: 10, height: 10, color: Colors.blue, @@ -47,7 +47,7 @@ void main() { width: 100, height: 100, alignment: Alignment.bottom, - child: Container( + Container( width: 10, height: 10, color: Colors.blue, @@ -63,7 +63,7 @@ void main() { width: 100, height: 100, alignment: Alignment.leading, - child: Container( + Container( width: 10, height: 10, color: Colors.blue, @@ -79,7 +79,7 @@ void main() { width: 100, height: 100, alignment: Alignment.trailing, - child: Container( + Container( width: 10, height: 10, color: Colors.blue, @@ -95,7 +95,7 @@ void main() { width: 100, height: 100, alignment: Alignment.topLeading, - child: Container( + Container( width: 10, height: 10, color: Colors.blue, @@ -111,7 +111,7 @@ void main() { width: 100, height: 100, alignment: Alignment.topTrailing, - child: Container( + Container( width: 10, height: 10, color: Colors.blue, @@ -127,7 +127,7 @@ void main() { width: 100, height: 100, alignment: Alignment.bottomLeading, - child: Container( + Container( width: 10, height: 10, color: Colors.blue, @@ -143,7 +143,7 @@ void main() { width: 100, height: 100, alignment: Alignment.bottomTrailing, - child: Container( + Container( width: 10, height: 10, color: Colors.blue, diff --git a/test/shapes/ellipse_golden_test.dart b/test/shapes/ellipse_golden_test.dart index 60c959d..dc2d77f 100644 --- a/test/shapes/ellipse_golden_test.dart +++ b/test/shapes/ellipse_golden_test.dart @@ -12,7 +12,7 @@ void main() { const Frame( width: 200, height: 100, - child: Ellipse( + Ellipse( fillColor: Colors.blue, ), ), @@ -22,7 +22,7 @@ void main() { const Frame( width: 200, height: 100, - child: Ellipse( + Ellipse( fillGradient: LinearGradient( colors: [Colors.yellow, Colors.orange], ), @@ -34,7 +34,7 @@ void main() { const Frame( width: 200, height: 100, - child: Ellipse( + Ellipse( strokeColor: Colors.blue, ), ), @@ -44,7 +44,7 @@ void main() { const Frame( width: 200, height: 100, - child: Ellipse( + Ellipse( strokeGradient: LinearGradient( colors: [Colors.yellow, Colors.orange], ), diff --git a/test/shapes/goldens/rectangle_smoke-test.png b/test/shapes/goldens/rectangle_smoke-test.png index 8ece611..db5086f 100644 Binary files a/test/shapes/goldens/rectangle_smoke-test.png and b/test/shapes/goldens/rectangle_smoke-test.png differ diff --git a/test/shapes/goldens/shadow_smoke-test.png b/test/shapes/goldens/shadow_smoke-test.png new file mode 100644 index 0000000..fec451c Binary files /dev/null and b/test/shapes/goldens/shadow_smoke-test.png differ diff --git a/test/shapes/rectangle_golden_test.dart b/test/shapes/rectangle_golden_test.dart index 4fd1ae9..0b940b2 100644 --- a/test/shapes/rectangle_golden_test.dart +++ b/test/shapes/rectangle_golden_test.dart @@ -12,7 +12,7 @@ void main() { const Frame( width: 200, height: 100, - child: Rectangle( + Rectangle( fillColor: Colors.blue, ), ), @@ -22,7 +22,7 @@ void main() { const Frame( width: 200, height: 100, - child: Rectangle( + Rectangle( fillGradient: LinearGradient( colors: [Colors.yellow, Colors.orange], ), @@ -34,7 +34,7 @@ void main() { const Frame( width: 200, height: 100, - child: Rectangle( + Rectangle( strokeColor: Colors.blue, ), ), @@ -44,7 +44,7 @@ void main() { const Frame( width: 200, height: 100, - child: Rectangle( + Rectangle( strokeGradient: LinearGradient( colors: [Colors.yellow, Colors.orange], ), diff --git a/test/shapes/shadow_golden_test.dart b/test/shapes/shadow_golden_test.dart new file mode 100644 index 0000000..0a681e9 --- /dev/null +++ b/test/shapes/shadow_golden_test.dart @@ -0,0 +1,59 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:golden_toolkit/golden_toolkit.dart'; +import 'package:swift_ui/swift_ui.dart'; + +void main() { + group("Shapes > Shadow >", () { + testGoldens("smoke test", (widgetTester) async { + final builder = GoldenBuilder.grid(columns: 2, widthToHeightRatio: 1) + ..addScenario( + 'Rectangle', + const Frame( + width: 200, + height: 100, + Shadow( + radius: 5, + x: 5, + y: 5, + Rectangle( + fillColor: Colors.blue, + ), + ), + ), + ) + ..addScenario( + 'Ellipse', + const Frame( + width: 200, + height: 100, + Shadow( + radius: 5, + x: 5, + y: 5, + Ellipse( + fillColor: Colors.blue, + ), + ), + ), + ) + ..addScenario( + 'Ellipse stroke', + const Frame( + width: 200, + height: 100, + Shadow( + radius: 5, + x: 5, + y: 5, + Ellipse( + strokeLineWidth: 5, + strokeColor: Colors.blue, + ), + ), + ), + ); + await widgetTester.pumpWidgetBuilder(builder.build()); + await screenMatchesGolden(widgetTester, 'shadow_smoke-test'); + }); + }); +}