microsoft / react-native-windows

A framework for building native Windows apps with React.
https://microsoft.github.io/react-native-windows/
Other
16.24k stars 1.14k forks source link

Drop Shadow support for View, Text, Image, and more #2800

Open RobMeyer opened 5 years ago

RobMeyer commented 5 years ago

Proposal: Drop Shadow support for View, Text, Image, and more

Implement support for ViewStyle.shadow and TextStyle.textShadow for View, Text, and Image.

Summary

Tasks

  1. A C++/winrt port of DropShadowPanel, to be called ShadowedContentControl will be used for shadows cast from all XAML-based components.
  2. Implement TextStyle.textShadow* support
  3. Implement ViewStyle.shadow support for ViewStyle.border and ViewStyle.backgroundColor props
  4. Implement ViewStyle.shadow* support for RN , ImageViewManager
  5. Optionally, extend and share ShadowedContentControl for reuse by third parties

Motivation

React Native supports shadows for View, Text, and Image using the ViewStyle.shadow and TextStyle.textShadow props. Android and iOS both support these properties (though with some notable differences), but support hasn’t been implemented in react-native-windows vNext yet. The WPF RN project has support but a straight port to UWP isn’t possible due to differences in shadow APIs available between WPF and UWP + XAML + Composition.

Shadows will improve application visuals and usability. In addition to aiding visualization of the application's information hierarchy and drawing attention to UX of interest, drop shadows can also be used to enhance readability of text against low-contrast or "busy" background content such as a photograph. A shadow without vertical or horizontal offsets can also be used to create a "glow" effect, used for similar design and usability goals.

Basic example

<View>
        <Text style={{shadowColor: "red", shadowRadius: 2, shadowOpacity: 1, shadowOffset: {width: 1, height: 1}}}>This is text using ViewStyle.shadow* props</Text>
        <Text style={{textShadowColor: 'red', textShadowRadius: 2, textShadowOffset: {width: 1, height: 1}}}>This is text using TextStyle.textShadow* props</Text>

        <Image source={{uri: 'https://cdn.freebiesupply.com/logos/large/2x/disney-1-logo-png-transparent.png'}} style={{width: 200, height: 200, shadowColor: "red", shadowRadius: 2, shadowOpacity: 1, shadowOffset: {width: 1, height: 1}}} />

        <View style={{width: 300, height: 50, backgroundColor: "blue", shadowColor: "red", shadowRadius: 2, shadowOpacity: 1, shadowOffset: {width: 1, height: 1}, marginBottom: 20}} />

        <View style={{width: 300, height: 250, borderColor: "blue", borderWidth: 5, borderRadius: 20, borderStyle: 'dotted', shadowColor: "red", shadowRadius: 2, shadowOpacity: 1, shadowOffset: {width: 1, height: 1}}}>
          <Text>Shadow on a view with dotted border. Child elements (text and image) inherit ViewStyle.shadow* props</Text>
          <Image source={{uri: 'https://cdn.freebiesupply.com/logos/large/2x/disney-1-logo-png-transparent.png'}} style={{width: 200, height: 200}} />
        </View>
</View>

RN DropShadow Android and iOS

Open Questions

View shadows, iOS, Android, and staging work

iOS and Android's support for shadows on elements differs in one important way: on iOS all the children of the view also get the same drop shadow visuals applied to them. On android, the view's shadow only affects the view's border and background visuals, not all the children too. This can be seen in the screenshot above.

Android's model seems ideal because it allows for the most flexibility. On iOS, if you want a View's border to have shadow visuals, but you don't want the children to, then you have to put the children in another superimposed view. On Android, you simply set the shadow props on all the elements you want to have shadows. Similarly, in the event where child and a parent should both have shadows, on Android you're able to use the specialized textShadow props on the Text, which visually look better than iOS's inherited ViewStyle.shadow* visuals (see first two Text shadows in screenshot above).

However, I can guess how iOS' behavior came about. If we were to implement ShadowedContentControl using the "SoftwareBitmap -> CompositionBrush -> AlphaMask" technique, the shadow visuals would apply not only to the View itself, but also all children.

With that background information out of the way, we can discuss work staging. Rewriting ViewViewManager to use Composition border rendering instead of a XAML control would be a substantial task. The fastest path to supporting View shadows is to use the "SoftwareBitmap -> CompositionBrush -> AlphaMask" technique. However, this would mean children inherit shadows, like iOS. It also would be inferior performance-wise. Luckily, shadows are opt-in so it's pay-for-play.

Long-term there seems to be agreement on adopting Composition for a plethora of benefits (smaller XAML tree, improved performance, support for ViewStyle.borderStyle "dotted" and "dashed", etc.), but ShadowedContentControl + SoftwareBitmap -> CompositionBrush -> AlphaMask could help us get to basic drop shadow support faster. The short-term work also wouldn't go wasted, because it accrues towards making ShadowedContentControl work with arbitrary XAML types, useful for third parties wanting shadows for their own custom NativeComponents written wrapping XAML components.

RN Glyph / NativeComponent "PLYIcon"

IconViewManager is currently implemented using Glyph, not a XAML . We would like shadow support for these elements too, but I'm not sure which implementation would be preferred. Some possibilities: a. If ShadowedContentContainer has support for arbitrary XAML content via the SoftwareBitmap -> CompositionBrush -> AlphaMask approach, Glyph could be wrapped in a ShadowedContentContainer. b. IconViewManager could be rewritten to use instead of Glyph. The TextBlock could then be wrapped using ShadowedContentContainer. c. IconViewMangager could be rewritten to be drawn using Composition with DropShadow. d. Alternatively, our client could migrate from Glyph-based icons to using or components.

Popup/Flyout and Context Menu

Shadows are also of interest for these components but I haven't investigated enough to include anything along with this proposal.

Further reading, dev plan notes

2020/10/28: Attaching investigation notes I put together back in July 2019 when I was formulating a plan of attack. These notes were only intended for personal purposes and haven't been cleaned up for broad publication, so grant me a little leeway regarding rough edges. I hope it helps anyone that might pick up this task in the future. Drop Shadows in react-native-windows.pdf

harinikmsft commented 5 years ago

Thanks for writing this up @RobMeyer !

One thing occurs off the top of my head - DropShadowPanel is a Windows Community toolkit control. Is the best approach here to wrap a high policy control or should we go directly to Windows.UI.Composition.DropShadow and implement a layer around that in RNW instead?

Let's discuss in an upcoming review.

RobMeyer commented 5 years ago

I think DropShadowPanel's approach is the only way that works because of some unfortunate restrictions of ElementCompositionPreview and DropShadow.

Only SpriteVisual and LayerVisual have a .Shadow property used to attach a shadow to an existing visual. Visual, which is what is exposed by ElementCompositionPreview, doesn't have a Shadow property.

It's still possible to create a new SpriteVisual and attach a DropShadow initialized using Mask (acquired from TextBlock.GetAlphaMask, Image.GetAlphaMask, or Shape.GetAlphaMask), but the SpriteVisual needs to be inserted in the XAML tree somewhere. Two ways to do this:

  1. Insert into a XAML element's Composition tree using ElementCompositionPreview::SetChildElementVisual. Unfortunately, this API doesn't give you control over the Z-Order. The Shadow always appears above the XAML content that is the source of the shadow.
  2. Create a sibling element (that is lower in Z-order) and set the Composition SpriteVisual, again using SetChildElementVisual. The second approach is eactly what DropShadowPanel does.
giregk commented 4 years ago

For your information, in iOS, children will not get the shadow property if a background color is set on the view ;)

juhasuni commented 1 year ago

Any chance of moving forward with this? I believe that the fact that shadow properties are still missing might come as a surprise for anyone coming to RNW development 🥸

vinaykumardetrix commented 8 months ago

can any one helps in creating shadow properties that has to be work on react native windows application using react native components or using svg components

for now i was using imagebackground that has image shadow and on top of that i was showing view data to overcome this any help from react native community