-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Views] - Implement Shadow widget (resolves #31) #32
base: main
Are you sure you want to change the base?
Conversation
@matthew-carroll Pinging you on this in case you didn't see it earlier. |
|
||
/// A widget that adds a shadow effect to its child widget. | ||
class Shadow extends StatelessWidget { | ||
/// Creates a Shadow widget with the given properties. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When there's only a single constructor, I tend to find that docs on the constructor don't help much. For example "Creates a Shadow widget with the given properties" is exactly what the code tell us, without docs.
Given that there's just one constructor, I suggest providing all relevant widget-wide details in the class Dart Doc, and then provide per property clarifications in the property docs.
For example, one thing I would expect to see explained in the class Dart Doc is how a Shadow
widget applies a shadow to its child. How does Shadow
know the shape of its child? Are there any further requirements to make this widget work? Etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I removed the constructor Dart Doc and improved the class one. I didn't try to explain how Shadow knows the shape of its child, though. That seems like an implementation detail that is unnecessary to know at a high level. Shadows in swift_ui just work no matter what the shape is (unlike in vanilla Flutter), so there is nothing special a developer needs to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exposing implementation details in docs is fine, so long as those details are directly related to the applicability of the object.
For example, let's say that I display a bitmap image of an oval with transparency. Then I apply a shadow. In Photoshop, I'll get a shadow around the oval. But in Figma I get a shadow around the rectangle of the whole bitmap. Shadows have internal details that leak out and we should make them clear so that people don't have the wrong expectations.
I assume that the underlying shadow mechanism in SwiftUI is based around some kind of vector graphics protocol, is that right? If you modify a bitmapw ith transparency in SwiftUI with a shadow, does the shadow apply to the image bounds, or the visible bitmap bounds?
Assuming that SwiftUI does the same thing that we do, we should at least document what that restriction is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand now. I updated the doc comments to reflect how the shadow works.
I'm not sure if SwiftUI uses a vector graphics protocol or not, but the shadow applies to the visible parts of the image, even for a bitmap with transparency. I tested this with the dash_hello image. Here it is in SwiftUI:
Image("dash_hello")
.resizable()
.aspectRatio(contentMode: .fit)
.shadow(color: .gray, radius: 10, x: 10, y: 10)
.frame(width: 200, height: 200)
And this is the Flutter version:
Frame(
width: 200,
height: 200,
Shadow(
color: Color(0xFF8E8E93),
radius: 10.0,
x: 10.0,
y: 10.0,
Image.asset(
'assets/images/dash_hello/dash_hello.png',
fit: BoxFit.contain,
),
),
),
The behavior is the same for both.
Widget build(BuildContext context) { | ||
return Stack( | ||
children: [ | ||
Transform.translate( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you check what Swift UI does with semi-translucent shapes that have a shadow applied?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When applying a shadow to a semi-translucent shape in SwiftUI, the shadow itself appears to retain its opacity:
Ellipse()
.fill(Color.yellow)
.shadow(color: .gray, radius: 10, x: 10, y: 10)
.frame(width: 200, height: 100)
Ellipse()
.fill(Color.yellow.opacity(0.3))
.shadow(color: .gray, radius: 10, x: 10, y: 10)
.frame(width: 200, height: 100)
This is not the case with the current implementation in Flutter. The shadow is also lightened based on the child's opacity:
Frame(
width: 200,
height: 100,
Shadow(
color: color,
radius: radius,
x: x,
y: y,
Ellipse(
fillColor: Colors.yellow,
),
),
),
Frame(
width: 200,
height: 100,
Shadow(
color: color,
radius: radius,
x: x,
y: y,
Ellipse(
fillColor: Colors.yellow.withOpacity(0.3),
),
),
),
Should we match the SwiftUI version? In my opinion, the Flutter version looks better, but if we want a pixel perfect copy, the answer is probably yes. If so, I'll need to think about how to do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If possible, let's do what SwiftUI does. The Flutter example that you included looks like what I'd expect if I applied an opacity around the whole widget tree, including the shadow. But if I only applied opacity to the shape's color, I wouldn't expect that to impact the shadow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the widget implementation so that the widget opacity and shadow opacity are now independent of each other. The flutter version now works like the SwiftUI version for partial transparency in the widget but full opacity for the inner shadow color:
The implementation had the side effect of removing antialiasing from the edges before the blurring step. So if the blur radius was 0.0, the jagged edges were noticeable. So in the case when radius is 0.0, I now retain the original widget opacity (including its antialiasing) for the shadow. That means there still is a rare edge case where someone uses a semitransparent widget with a 0-radius shadow. In that case the shadow would also be semitransparent. This would be quite rare and a slightly more transparent shadow wouldn't look bad (in my opinion), so I recommend we don't worry about it for now.
@matthew-carroll Ready for another review. |
This PR adds a
Shadow
widget to mimic the behavior of the SwiftUI.shadow
modifier method. The reason for implementingShadow
as a composable Widget rather than a property on a shape is that SwiftUI'sshadow
is aView
modifier method. You can add a shadow to anything in SwiftUI, not just a shape.You can see the results below. SwiftUI is first and Flutter second:
Because non-rectangular shapes can have shadows, I wasn't able to use Flutter's
BoxShadow
.Shadow
itself is a composite of a few new widgets:Blur
Tint
Desaturate
I haven't exported those widgets yet, but
tint
is another SwiftUI property, so we may want to export this widget as well in the future.I discovered I had made a mistake on the
Rectangle
stroke. It actually is centered on the edge line, not contained completely within the rectangle. I had missed that before because of SwiftUI's clipping behavior. This means thatEllipse
andRectangle
have the same stroke behavior. I snuck this fix into this PR since it related to drawing shadows. You can see the width of the rectangle with the thick stroke is wider than the other rectangles. This is the proper behavior: