Skip to content

Commit

Permalink
[Shapes] - Implement Ellipse (resolves #29) (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
suragch authored Jun 13, 2024
1 parent 8ee6660 commit 9262998
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
7179CF852B26DEDD00C5927B /* TextAccessibilityPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179CF842B26DEDD00C5927B /* TextAccessibilityPage.swift */; };
7179CF872B26DF0E00C5927B /* TextLocalizationPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179CF862B26DF0E00C5927B /* TextLocalizationPage.swift */; };
719116892B216D500007C4DE /* TextPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719116882B216D500007C4DE /* TextPage.swift */; };
71C346EF2C1A7B580087A514 /* EllipseExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71C346EE2C1A7B580087A514 /* EllipseExamples.swift */; };
B3CBD1552B2E18320095DE1F /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B3CBD1542B2E18320095DE1F /* Localizable.xcstrings */; };
B3CBD1572B2E1BE20095DE1F /* LocalizableAlternative.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B3CBD1562B2E1BE20095DE1F /* LocalizableAlternative.xcstrings */; };
B3DD96702B66513800F66E9F /* ImageLocalizationPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3DD966F2B66513800F66E9F /* ImageLocalizationPage.swift */; };
Expand Down Expand Up @@ -59,6 +60,7 @@
7179CF842B26DEDD00C5927B /* TextAccessibilityPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextAccessibilityPage.swift; sourceTree = "<group>"; };
7179CF862B26DF0E00C5927B /* TextLocalizationPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLocalizationPage.swift; sourceTree = "<group>"; };
719116882B216D500007C4DE /* TextPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextPage.swift; sourceTree = "<group>"; };
71C346EE2C1A7B580087A514 /* EllipseExamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EllipseExamples.swift; sourceTree = "<group>"; };
B3CBD1542B2E18320095DE1F /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
B3CBD1562B2E1BE20095DE1F /* LocalizableAlternative.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = LocalizableAlternative.xcstrings; sourceTree = "<group>"; };
B3DD966F2B66513800F66E9F /* ImageLocalizationPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLocalizationPage.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -114,6 +116,7 @@
children = (
714951442C0EB31C00818B06 /* ShapesPage.swift */,
714951462C0EB52400818B06 /* RectangleExamples.swift */,
71C346EE2C1A7B580087A514 /* EllipseExamples.swift */,
);
path = shapes;
sourceTree = "<group>";
Expand Down Expand Up @@ -434,6 +437,7 @@
C9FB17582C0C4D25004479AA /* ZStackExamples.swift in Sources */,
714951452C0EB31C00818B06 /* ShapesPage.swift in Sources */,
C902DDE72AE3904400242DBA /* TodoPage.swift in Sources */,
71C346EF2C1A7B580087A514 /* EllipseExamples.swift in Sources */,
C9CAE2B32AE1066B0042DBC7 /* MotionPage.swift in Sources */,
B3DD96702B66513800F66E9F /* ImageLocalizationPage.swift in Sources */,
C9FB17562C0C4AB4004479AA /* HStackExamples.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import SwiftUI

struct EllipsePage: View {
var body: some View {
ScrollView {
VStack(spacing: 20) {
// Ellipse with solid fill
Ellipse()
.fill(Color.blue)
.frame(width: 200, height: 100)

// Ellipse with gradient fill
Ellipse()
.fill(LinearGradient(gradient: Gradient(colors: [.yellow, .orange]), startPoint: .leading, endPoint: .trailing))
.frame(width: 200, height: 100)

// Ellipse with solid stroke
Ellipse()
.stroke(Color.red, lineWidth: 4)
.frame(width: 200, height: 100)

// Ellipse with gradient stroke
Ellipse()
.stroke(LinearGradient(gradient: Gradient(colors: [.yellow, .blue]), startPoint: .leading, endPoint: .trailing), lineWidth: 14)
.frame(width: 200, height: 100)
}.frame(width: 300)
}
}
}

struct EllipsePage_Previews: PreviewProvider {
static var previews: some View {
EllipsePage()
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ struct ShapesPage: View {
} label: {
Text("Rectangle")
}

NavigationLink {
EllipsePage()
} label: {
Text("Ellipse")
}
}.navigationTitle("Shapes")
}
}
Expand Down
58 changes: 58 additions & 0 deletions example/lib/shapes/ellipse.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:flutter/widgets.dart';
import 'package:swift_ui/swift_ui.dart';

class EllipseDemo extends StatelessWidget {
const EllipseDemo({
super.key,
});

@override
Widget build(BuildContext context) {
return const VStack(
[
// Ellipse with solid fill
Frame(
width: 200,
height: 100,
child: Ellipse(
fillColor: Colors.blue,
),
),

// Ellipse with gradient fill
Frame(
width: 200,
height: 100,
child: Ellipse(
fillGradient: LinearGradient(
colors: [Colors.yellow, Colors.orange],
),
),
),

// Ellipse with solid stroke
Frame(
width: 200,
height: 100,
child: Ellipse(
strokeColor: Colors.red,
strokeLineWidth: 4.0,
),
),

// Ellipse with gradient stroke
Frame(
width: 200,
height: 100,
child: Ellipse(
strokeGradient: LinearGradient(
colors: [Colors.yellow, Colors.blue],
),
strokeLineWidth: 14.0,
),
),
],
spacing: 20,
);
}
}
6 changes: 6 additions & 0 deletions example/lib/shapes/shapes_examples.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:example/infrastructure/inventory_page.dart';
import 'package:example/shapes/rectangle.dart';
import 'package:flutter/cupertino.dart';

import 'ellipse.dart';

class ShapesPage extends StatelessWidget {
const ShapesPage({super.key});

Expand All @@ -17,6 +19,10 @@ class ShapesPage extends StatelessWidget {
label: "Rectangle",
pageBuilder: createDemo(const RectangleDemo()),
),
InventoryItem(
label: "Ellipse",
pageBuilder: createDemo(const EllipseDemo()),
),
],
),
],
Expand Down
130 changes: 128 additions & 2 deletions lib/src/shapes/ellipse.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,136 @@
import 'package:flutter/widgets.dart';

/// An elliptical shape whose width and height match size of the parent
class Ellipse extends StatelessWidget {
const Ellipse({super.key});
const Ellipse({
super.key,
this.fillColor,
this.fillGradient,
this.strokeColor,
this.strokeGradient,
this.strokeLineWidth = 1.0,
});

/// A color used for painting the interior of the ellipse.
///
/// This is an alternative to [fillGradient].
final Color? fillColor;

/// A gradient used for painting the interior of the ellipse.
///
/// This is an alternative to [fillColor].
final Gradient? fillGradient;

/// A color used for painting the outline of the ellipse.
///
/// This is an alternative to [strokeGradient].
final Color? strokeColor;

/// A gradient used for painting the outline of the ellipse.
///
/// This is an alternative to [strokeColor].
final Gradient? strokeGradient;

/// The width of the stroke used to paint the outline of the ellipse.
///
/// The stroke line is centered along the edge of the ellipse. Half of
/// [strokeLineWidth] is painted outside of the ellipse and half inside.
///
/// The default is 1.0.
final double strokeLineWidth;

@override
Widget build(BuildContext context) {
return const Placeholder();
return CustomPaint(
size: Size.infinite,
painter: _EllipsePainter(
fillColor: fillColor,
fillGradient: fillGradient,
strokeColor: strokeColor,
strokeGradient: strokeGradient,
strokeLineWidth: strokeLineWidth,
),
);
}
}

class _EllipsePainter extends CustomPainter {
_EllipsePainter({
required this.fillColor,
required this.fillGradient,
required this.strokeColor,
required this.strokeGradient,
required this.strokeLineWidth,
});

final Color? fillColor;
final Gradient? fillGradient;
final Color? strokeColor;
final Gradient? strokeGradient;
final double strokeLineWidth;

@override
void paint(Canvas canvas, Size size) {
_paintFill(canvas, size);
_paintStroke(canvas, size);
}

void _paintFill(Canvas canvas, Size size) {
if (fillColor != null) {
_paintSolidFill(canvas, size);
} else if (fillGradient != null) {
_paintGradientFill(canvas, size);
}
}

void _paintSolidFill(Canvas canvas, Size size) {
final rect = Rect.fromLTWH(0, 0, size.width, size.height);
final paint = Paint()
..style = PaintingStyle.fill
..color = fillColor!;
canvas.drawOval(rect, paint);
}

void _paintGradientFill(Canvas canvas, Size size) {
final rect = Rect.fromLTWH(0, 0, size.width, size.height);
final paint = Paint()
..style = PaintingStyle.fill
..shader = fillGradient!.createShader(rect);
canvas.drawOval(rect, paint);
}

void _paintStroke(Canvas canvas, Size size) {
if (strokeColor != null) {
_paintSolidStroke(canvas, size);
} else if (strokeGradient != null) {
_paintGradientStroke(canvas, size);
}
}

void _paintSolidStroke(Canvas canvas, Size size) {
final rect = Rect.fromLTWH(0, 0, size.width, size.height);
final paint = Paint()
..style = PaintingStyle.stroke
..color = strokeColor!
..strokeWidth = strokeLineWidth;
canvas.drawOval(rect, paint);
}

void _paintGradientStroke(Canvas canvas, Size size) {
final rect = Rect.fromLTWH(0, 0, size.width, size.height);
final paint = Paint()
..style = PaintingStyle.stroke
..shader = strokeGradient!.createShader(rect)
..strokeWidth = strokeLineWidth;
canvas.drawOval(rect, paint);
}

@override
bool shouldRepaint(covariant _EllipsePainter oldDelegate) {
return oldDelegate.fillColor != fillColor ||
oldDelegate.fillGradient != fillGradient ||
oldDelegate.strokeColor != strokeColor ||
oldDelegate.strokeGradient != strokeGradient ||
oldDelegate.strokeLineWidth != strokeLineWidth;
}
}
58 changes: 58 additions & 0 deletions test/shapes/ellipse_golden_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:flutter/widgets.dart';
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 > Ellipse >", () {
testGoldens("smoke test", (widgetTester) async {
final builder = GoldenBuilder.grid(columns: 2, widthToHeightRatio: 1)
..addScenario(
'Solid fill',
const Frame(
width: 200,
height: 100,
child: Ellipse(
fillColor: Colors.blue,
),
),
)
..addScenario(
'Gradient fill',
const Frame(
width: 200,
height: 100,
child: Ellipse(
fillGradient: LinearGradient(
colors: [Colors.yellow, Colors.orange],
),
),
),
)
..addScenario(
'Solid stroke',
const Frame(
width: 200,
height: 100,
child: Ellipse(
strokeColor: Colors.blue,
),
),
)
..addScenario(
'Gradient stroke',
const Frame(
width: 200,
height: 100,
child: Ellipse(
strokeGradient: LinearGradient(
colors: [Colors.yellow, Colors.orange],
),
),
),
);
await widgetTester.pumpWidgetBuilder(builder.build());
await screenMatchesGolden(widgetTester, 'ellipse_smoke-test');
});
});
}
Binary file added test/shapes/goldens/ellipse_smoke-test.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 9262998

Please sign in to comment.