Skip to content
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

[Specs] ShadowContainer #470

Closed
roubachof opened this issue Feb 6, 2023 · 14 comments · Fixed by #638, #703, #717 or #739
Closed

[Specs] ShadowContainer #470

roubachof opened this issue Feb 6, 2023 · 14 comments · Fixed by #638, #703, #717 or #739
Assignees
Labels

Comments

@roubachof
Copy link
Contributor

roubachof commented Feb 6, 2023

Shadows for Uno specs

Name of the component: ShadowContainer
Type: ContentControl

Properties

Property Type Description
Shadows ShadowCollection The list of shadows applied to the child of the container
Child UIElement The element that is casting the shadow

Shadow

Type: DependencyObject

Property Type Description
OffsetX double The shadow X offset
OffsetY double The shadow Y offset
Color Color The shadow Color
BlurRadius double The amount of blur applied to the Shadow [0..100]
Spread double The amount the shadow should be inflated prior to applying the blur
Inner bool Shadows are drawn within the input content

Implementation

   +-Canvas---------------------------+
   |                                  |
   |  +-SKCanvasView---------------|  |
   |  |                            |  |
   |  |   +-Shadow-----+           |  |
   |  |   +-Shadow-----+           |  |
   |  |   +-Shadow-----+           |  |
   |  |                            |  |
   |  +-Child----------------------+  |
   |  |                            |  |
   |  |                            |  |
   |  |                            |  |
   |  +----------------------------+  |
   |                                  |
   +----------------------------------+

Generic.xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:toolkit="using:Uno.Toolkit.UI">

    <Style TargetType="toolkit:DropShadowPanel">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="toolkit:DropShadowPanel">
                    <Canvas>
                        <SKCanvasView x:Name="PART_Shadows" />
                        
                        <ContentPresenter ContentTemplate="{TemplateBinding Child}" />
                    </Canvas>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

Shadows drawing would occurs thanks to SkiaSharp:

  1. We'll have the same result for each platform
  2. We could benefit from hardware acceleration from OpenGL or even Vulkan (Create a new cross-platform view that supports different GPU backends mono/SkiaSharp#2104)

Remarks

If the child is an image or text, we need to get the alpha mask or clipping path.
If the child is Border or Control, we can just apply the corner radius to the shadows.
In v1, I suggest we stick to simple child with corner radius.

Usage

Declare shadows in dictionary

<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms" xmlns:toolkit="using:Uno.Toolkit.UI">

    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Colors.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <toolkit:ShadowCollection x:Key="NoShadow" />

    <toolkit:ShadowCollection x:Key="DarkNeumorphism">
       <toolkit:Shadow
            BlurRadius="3"
            Opacity="1"
            Offset="5,5"
            Color="#2b2b2b" />
        <toolkit:Shadow
            BlurRadius="3"
            Opacity="1"
            Offset="-5,-5"
            Color="#3a3a3a" />
    </toolkit:ShadowCollection>

</ResourceDictionary>

Use shadows with resources

<ContentPage.Resources>
        <ResourceDictionary>
            <DataTemplate x:Key="VerticalDudeTemplate">
                <toolkit:DraggableViewCell x:Name="DraggableViewCell">
                    <toolkit:ShadowContainer
                        Shadows="{StaticResource DarkNeumorphism}">
                        <views:SillyListCell
                            Margin="16,13,16,13"
                            BackgroundColor="{StaticResource DarkerSurface}"
                            CornerRadius="10">
                            <views:SillyListCell.Triggers>
                                <DataTrigger
                                    Binding="{Binding Source={x:Reference DraggableViewCell}, Path=IsDragAndDropping}"
                                    TargetType="views:SillyListCell"
                                    Value="True">
                                    <Setter Property="BackgroundColor" Value="{StaticResource DarkSurface}" />
                                </DataTrigger>
                            </views:SillyListCell.Triggers>
                        </views:SillyListCell>
                    </toolkit:ShadowContainer>
                </toolkit:DraggableViewCell>
            </DataTemplate>

            ...
        </ResourceDictionary>
    </ContentPage.Resources>

image

Use shadows directly

<Grid RowSpacing="10">
    <Grid.RowDefinitions>
        <RowDefinition x:Name="Description" Height="Auto" />
        <RowDefinition Height="100" />
        <RowDefinition Height="80" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>

    <Label Grid.Row="0"
           Grid.Column="0"
           Grid.ColumnSpan="2"
           Style="{StaticResource TextBodySecondary}"
           Margin="15,15"
           Text="Enter a world of colored shadows." />

        <toolkit:ShadowContainerx:Name="ButtonPlusColoredShadows"
                                  Grid.Row="1"
                                  Grid.Column="0">
        <toolkit:ShadowContainer.Shadows>
            <toolkit:ShadowCollection>
                <toolkit:Shadow BlurRadius="10"
                                Opacity="0.7"
                                Offset="0,10"
                                Color="Violet" />
            </toolkit:ShadowCollection>
        </toolkit:ShadowContainer.Shadows>
        <ImageButton Width="60"
                     Height="60"
                     Padding="20"
                     HorizontalAlignment="Center"
                     VerticalAlignment="Center"
                     BackgroundColor="Violet"
                     Clicked="ImageButtonOnClicked"
                     CornerRadius="30"
                     Source="{StaticResource IconPlusWhite}" />
    </toolkit:ShadowContainer>

    <toolkit:ShadowContainer Grid.Row="1"
                              Grid.Column="1"
                              Margin="0,10,0,0">
        <toolkit:ShadowContainer.Shadows>
            <toolkit:ShadowCollection>
                <toolkit:Shadow BlurRadius="10"
                                Opacity="0.5"
                                Offset="8,8"
                                Color="Yellow" />
                <toolkit:Shadow BlurRadius="10"
                                Opacity="0.5"
                                Offset="-8,-6"
                                Color="Violet" />
            </toolkit:ShadowCollection>
        </toolkit:ShadowContainer.Shadows>
        <Image Width="120"
               Margin="-20,0,0,0"
               HorizontalAlignment="Center"
               VerticalAlignment="Center"
               Source="{images:ImageResource nyan_cat.png}" />
    </sho:ShadowContainer>

image

Questions

  • I don't know if a SKCanvasView exist for UNO?
  • Do we have a xplat implementation of GetAlphaMask or a GetClippingPath for v2?
@roubachof roubachof added kind/enhancement New feature or request. triage/untriaged Indicates an issue requires triaging or verification. labels Feb 6, 2023
@NVLudwig
Copy link

NVLudwig commented Feb 6, 2023

Is it ok that these two share the same TYPE value? (NonDEV asking)
`

BlurRadius double The amount of blur applied to the Shadow [0..100]
Spread double The amount the shadow should be inflated prior to applying the blur

`

@NVLudwig
Copy link

NVLudwig commented Feb 6, 2023

Questions (that may include scope creep)
Does offset include both X and Y values? (should)
Does Colour include transparency? (should)

Is there a way to get global light source set for multiple elements? (Nice to have)
A child can have multiple shadows right (should)?
image

@roubachof
Copy link
Contributor Author

@NVLudwig yes to all questions except for light source.
Here we are only creating shadows.
The shadows could be the result of a light source.
We could create a neumorphism component using the shadows component.

@jeromelaban
Copy link
Member

Regarding a Skia-based backend, we'd likely need to have some sort of ShadowPresenter to allow the Skia canvas switching (SKXamlCanvas vs. the SKSwapChainPanel, where SKSwapChainPanel is not available on all targets), or even use native shadow primitives where it could make sense.

@roubachof
Copy link
Contributor Author

roubachof commented Feb 14, 2023

Support of colored shadows in native platforms

Android

Element Type Multiple GPU Implementation Since
Rectangle Canvas setMaskFilter()
CornerRadius Canvas setMaskFilter()
drawRoundRect()
API21
Inner shadow
Shape Canvas setMaskFilter()
drawPath()
Text Canvas extractAlpha()
setMaskFilter()
Text Native setShadowLayer()
Image Canvas extractAlpha()
setMaskFilter()

MAUI implementation:

Hardware acceleration is possible:

iOS

Element Type Multiple GPU Implementation
Rectangle CALayer CALayer.shadowXXX
CornerRadius CALayer CALayer.shadowPath
Inner shadow
Shape CALayer CALayer.shadowPath
Text CALayer label.layer.shadowXXX
maybe multiple shadows would work with sublayers?
Image CALayer should work like label (uncertain)

MAUI implementation:

For good rendering perf, you need to set the shadowPath property on the CALayer, or else you will have a context switch between onscreen and offscreen rendering.
source: http://www.lukeparham.com/blog/2018/3/6/friends-dont-let-friends-render-offscreen

Inner shadow references:

WinUI

Element Type Multiple GPU Implementation
Rectangle Composition API SpriteVisual+CreateDropShadow()
CornerRadius Composition API SpriteVisual+CreateDropShadow()
Inner shadow
Shape Composition API GetAlphaMask()
Text Composition API GetAlphaMask()
Image Composition API GetAlphaMask()

MAUI implementation:

Other implementations:

SkiaSharp

Element Type Multiple GPU Implementation
Rectangle SKCanvas 🧪 CreateDropShadow()
CornerRadius SKCanvas 🧪 CreateDropShadow()
DrawRoundRect()
Inner shadow SKCanvas 🧪 CreateDropShadow()
Shape SKCanvas 🧪 convert path to skia path
Text
Image
GitHub
.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop. - maui/WrapperView.cs at 83c3ac902ea496e409026c830ccd930c4021f...
GitHub
.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop. - maui/WrapperView.cs at 83c3ac902ea496e409026c830ccd930c4021f...
Luke Parham
Have you ever been using an app and a certain feature just seemed to be overwhelmingly sluggish compared to the rest of the app? I'm not here to judge (I totally am), but a lot of times scroll view animations can really suffer in apps due to developers using techniques that are less than ideal. If
Stack Overflow
I have the following CALayer:

CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = CGRectMake(8, 57, 296, 30);
gradient.cornerRadius = 3.0f;
gradient.colors = [NSArray arrayWithOb...

Stack Overflow
I have the task to implement neumorphic design styles in my UIKit application. I have successfully implemented the double outer shadows (dark and light), but I somehow, can't figure out how to impl...
GitHub
.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop. - maui/WrapperView.cs at 83c3ac902ea496e409026c830ccd930c4021f...
GitHub
The Windows Community Toolkit is a collection of helpers, extensions, and custom controls. It simplifies and demonstrates common developer tasks building .NET apps with UWP and the Windows App SDK ...
GitHub
Add as many custom shadows (Color, Offset, Blur, Neumorphism) as you like to any Xamarin.Forms view (Android, iOS, UWP). - Sharpnado.Shadows/UWPShadowsController.cs at master · roubachof/Sharpnado....
Nick's .NET Travels
Following Part 1 – ThemeShadow (and Part 1b – Lists) in this post we’re going to look at a very simple example of creating your own shadow. I’m going to reuse my simple example of two overlapping rectangles. The goal is to: Add a shadow around the bottom-left rectangle The shadow should elevate the rectangle ... Read more
GitHub
Add as many custom shadows (Color, Offset, Blur, Neumorphism) as you like to any Xamarin.Forms view (Android, iOS, UWP). - Sharpnado.Shadows/TizenShadowsRenderer.cs at master · roubachof/Sharpnado....
GitHub
Controls for Xamarin Forms based on neumorphism tendency - Xamarin.Forms.NeoControls/NeoRoundedView.cs at master · felipebaltazar/Xamarin.Forms.NeoControls

@francoistanguay
Copy link
Contributor

francoistanguay commented Mar 6, 2023

Thoughts on simplifying the name?
Could we just call it Shadow like we have AutoLayout and SafeArea?

If not, I'd be ok on agreeing on a suffix, just need to discuss if it's Panel/Control/View.

And then if we have Shadow, could we have ShadowItem like there is SwipeItem?

@NVLudwig
Copy link

NVLudwig commented Mar 6, 2023

@francoistanguay 2 cents:
If implement ShadowItem would it conflict with SwipeItem, could an item have both properties?
e.g.: If I want an item in a list like a Card to swipe AND have a shadow would that be possible?
https://m3.material.io/styles/elevation/applying-elevation#6f6ebce2-5731-48f1-8d92-37341cc5aee9
image

@kazo0
Copy link
Contributor

kazo0 commented Mar 6, 2023

We can have attached properties for the shadows, sort of like there is with <Grid /> and Grid.Row/Grid.Column. So the element itself will be responsible for informing its parent (the DropShadowsPanel) of how it should be presented in the UI

<utu:DropShadowsPanel>
    <Image Width="120"
           Margin="-20,0,0,0"
           HorizontalAlignment="Center"
           VerticalAlignment="Center"
           Source="{images:ImageResource nyan_cat.png}">
        <utu:DropShadowsPanel.Shadows>
            <utu:Shadow BlurRadius="10"
                        Opacity="0.5"
                        Offset="8,8"
                        Color="Yellow" />
            <utu:Shadow BlurRadius="10"
                        Opacity="0.5"
                        Offset="-8,-6"
                        Color="Violet" />
        </utu:DropShadowsPanel.Shadows>
    </Image>
</utu:DropShadowsPanel>

or

<utu:DropShadowsPanel>
    <Image Width="120"
            Margin="-20,0,0,0"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Source="{images:ImageResource nyan_cat.png}"
            utu:DropShadowsPanel.Shadows="{StaticResource DarkNeumorphism}" />
</utu:DropShadowsPanel>

And perhaps we can allow for inividual Shadow component properties as attached properties as well and infer a default SingleShadow with those property values. Something like:

<utu:DropShadowsPanel>
    <Image Width="120"
            Margin="-20,0,0,0"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Source="{images:ImageResource nyan_cat.png}"
            utu:DropShadowsPanel.BlurRadius="10"
            utu:DropShadowPanel.Offset="-10, 0"
            utu:DropShadowPanel.Color="Violet" />
</utu:DropShadowsPanel>

could result in a SingleShadow being added to the DropShadowPanel with the properties attached to the child element

And so would we need to define a separate DropShadowPanel for each control that we want to display a shadow? So if we had a more complex layout and we want to be able to display a shadow for multiple controls within that layout, would it be smart enough to handle something like:

<utu:DropShadowsPanel Grid.Row="1"
                        Grid.Column="1"
                        Margin="0,10,0,0">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Button Content="TopArea with Shadow">
            <utu:DropShadowsPanel.Shadows>
                <utu:Shadow BlurRadius="10"
                            Opacity="0.5"
                            Offset="8,8"
                            Color="Yellow" />
            </utu:DropShadowsPanel.Shadows>
        </Button>
        <Button Grid.Row="1"
                VerticalAlignment="Center"
                Content="MiddleArea without Shadow" />
        <Button Grid.Row="2"
                Content="BottomArea with Shadow">
            <utu:DropShadowsPanel.Shadows>
                <utu:Shadow BlurRadius="10"
                            Opacity="0.5"
                            Offset="8,8"
                            Color="Pink" />
            </utu:DropShadowsPanel.Shadows>
        </Button>
    </Grid>
</utu:DropShadowsPanel>

@kazo0
Copy link
Contributor

kazo0 commented Mar 6, 2023

As for naming, ShadowPanel maybe?

So then the child attached properties could be ShadowPanel.BlurRadius

Or perhaps ShadowSurface to depict that this would be the layer that the control's shadow is projected onto

@roubachof
Copy link
Contributor Author

roubachof commented Mar 7, 2023

For naming, with XF Shadows I named Shade a single shadow "item", and I felt bad about it since :)
I feel a shadow, should really be named Shadow and nothing else.
As per the container, I don't really have a preference between the suffixes.
I guess we should follow WinUI convention for it.

For the xaml part, my preference leans toward sticking to simplicity and simply have a list of Shadow , under the Shadows property.
Cause I feel defining shadows will be done 90% of the time in a Dictionary.
when a designer create a design system, he will apply the same shadow "style" to every item.
So most of the time you will end up with this:

<tk:ShadowContainer Shadows={StaticResource CardShadow}> .... 

@Xiaoy312
Copy link
Contributor

Xiaoy312 commented Mar 8, 2023

Would this means that, toolkit will have an additional package dependency for non-skia targets?

@roubachof roubachof changed the title [Specs] DropShadowsPanel [Specs] ShadowsContainer Mar 16, 2023
@roubachof roubachof changed the title [Specs] ShadowsContainer [Specs] ShadowContainer Apr 20, 2023
@agneszitte agneszitte linked a pull request Jul 26, 2023 that will close this issue
6 tasks
@agneszitte agneszitte added control/shadows and removed triage/untriaged Indicates an issue requires triaging or verification. labels Jul 26, 2023
@agneszitte agneszitte linked a pull request Aug 15, 2023 that will close this issue
@agneszitte agneszitte reopened this Aug 15, 2023
@agneszitte
Copy link
Contributor

agneszitte commented Aug 15, 2023

Re-Open for backport PRs (can be closed accordingly when merged)

@agneszitte
Copy link
Contributor

Related Epic issue: unoplatform/uno#6183

@agneszitte agneszitte linked a pull request Aug 21, 2023 that will close this issue
@agneszitte
Copy link
Contributor

Closing the issue as related PRs are now backported

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment