Attached Shadows (Composition and Win2D + Animations) (#4179)

## Fixes #3122 #3607 #3516

_Also implements #3693 for the new DropShadow._

FYI @seanocali as this is a different implementation approach (which is simpler to use outside of the DropShadowPanel we've been working on) but should hopefully achieve the same result.

This PR adds attached shadows which can easily be attached to any FrameworkElement without needing to modify the layout like DropShadowPanel does today. They can also be shared using a resource, added to the style of an element, and animated! All the things! 🎉

## PR Type

What kind of change does this PR introduce?

<!-- Please uncomment one or more options below that apply to this PR. -->

<!-- - Bugfix -->
- Feature by @Ryken100 and integrated/extended by @michael-hawker 
<!-- - Code style update (formatting) -->
<!-- - Refactoring (no functional changes, no api changes) -->
<!-- - Build or CI related changes -->
<!-- - Documentation content changes -->
<!-- - Sample app changes -->
<!-- - Other... Please describe: -->

## What is the current behavior?

DropShadowPanel is clunky and requires modifying how you layout your app.

## What is the new behavior?

Just attach a shadow and be done! (DropShadowPanel is deprecated.)

## PR Checklist

- [x] Composition Only Shadow Support? (with Target)
- [x] Add XML Docs
- [x] Animation Support to Explicit Animation System?
- [x] Bug can't use `AttachedCardShadow` directly with `Border`?

Please check if your PR fulfills the following requirements: <!-- and remove the ones that are not applicable to the current PR -->

- [ ] Tested code with current [supported SDKs](../#supported)
- [ ] Pull Request has been submitted to the documentation repository [instructions](../blob/main/Contributing.md#docs). Link: <!-- docs PR link -->
- [ ] Sample in sample app has been added / updated (for bug fixes / features)
  - [ ] Icon has been created (if new sample) following the [Thumbnail Style Guide and templates](https://github.com/CommunityToolkit/WindowsCommunityToolkit-design-assets)
- [ ] New major technical changes in the toolkit have or will be added to the [Wiki](https://github.com/CommunityToolkit/WindowsCommunityToolkit/wiki) e.g. build changes, source generators, testing infrastructure, sample creation changes, etc...
- [ ] Tests for the changes have been added (for bug fixes / features) (if applicable)
- [ ] Header has been added to all new source files (run _build/UpdateHeaders.bat_)
- [ ] Contains **NO** breaking changes

<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below.
Please note that breaking changes are likely to be rejected within minor release cycles or held until major versions. -->

## Other information

<!-- Please add any other information that might be helpful to reviewers. -->
This commit is contained in:
msftbot[bot] 2021-08-27 23:09:34 +00:00 коммит произвёл GitHub
Родитель fe9ac8971a e418928cd9
Коммит 5d5d86fd7d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
45 изменённых файлов: 2083 добавлений и 129 удалений

Просмотреть файл

@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using System.Numerics;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace Microsoft.Toolkit.Uwp.SampleApp.Common
{
public class Vector3Converter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is string)
{
return value;
}
var thickness = (Vector3)value;
return thickness.ToString().TrimStart('<').Replace(" ", string.Empty).TrimEnd('>');
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is string vectorString)
{
var vectorTokens = vectorString.Split(',')
.Where(tkn => !string.IsNullOrWhiteSpace(tkn))
.ToArray();
switch (vectorTokens.Length)
{
case 1:
var vectorValue = float.Parse(vectorString);
return new Vector3(vectorValue);
case 2:
var xValue = float.Parse(vectorTokens[0]);
var yValue = float.Parse(vectorTokens[1]);
return new Vector3(xValue, yValue, 0);
case 3:
return new Vector3(
float.Parse(vectorTokens[0]),
float.Parse(vectorTokens[1]),
float.Parse(vectorTokens[2]));
default:
return default(Vector3);
}
}
return value.ToString();
}
}
}

Просмотреть файл

@ -179,6 +179,14 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.Controls
converter = new ThicknessConverter();
break;
case PropertyKind.Vector3:
var vectorTextBox = new TextBox { Text = (propertyDict[option.Name] as ValueHolder).Value.ToString() };
controlToAdd = vectorTextBox;
dependencyProperty = TextBox.TextProperty;
converter = new Vector3Converter();
break;
default:
var textBox = new TextBox { Text = (propertyDict[option.Name] as ValueHolder).Value.ToString() };

Просмотреть файл

@ -316,10 +316,10 @@
<Content Include="SamplePages\FocusTracker\FocusTracker.png" />
<Content Include="SamplePages\BladeView\BladeView.png" />
<Content Include="SamplePages\Carousel\Carousel.png" />
<Content Include="SamplePages\DropShadowPanel\DropShadowPanel.png" />
<Content Include="SamplePages\Shadows\DropShadowPanel.png" />
<Content Include="SamplePages\Expander\Expander.png" />
<Content Include="SamplePages\DropShadowPanel\Trex.png" />
<Content Include="SamplePages\DropShadowPanel\Unicorn.png" />
<Content Include="SamplePages\Shadows\Trex.png" />
<Content Include="SamplePages\Shadows\Unicorn.png" />
<Content Include="SamplePages\GazeInteraction\GazeInteraction.png" />
<Content Include="SamplePages\GazeTracing\GazeTracing.png" />
<Content Include="SamplePages\GridSplitter\GridSplitter.png" />
@ -398,7 +398,7 @@
<Content Include="SamplePages\ImageCache\ImageCacheXaml.bind" />
<Content Include="SamplePages\Incremental Loading Collection\IncrementalLoadingCollectionCode.bind" />
<Content Include="SamplePages\ImageCache\ImageCacheCode.bind" />
<Content Include="SamplePages\DropShadowPanel\DropShadowPanelXaml.bind" />
<Content Include="SamplePages\Shadows\DropShadowPanelXaml.bind" />
<Content Include="SamplePages\Object Storage\ObjectStorageCode.bind" />
<Content Include="SamplePages\BackgroundTaskHelper\BackgroundTaskHelperCode.bind" />
<Content Include="SamplePages\ListDetailsView\ListDetailsView.bind" />
@ -489,6 +489,7 @@
<Content Include="SamplePages\DataGrid\DataGridCode.bind" />
<Content Include="SamplePages\ViewportBehavior\ViewportBehaviorCode.bind" />
<Compile Include="Common\TextBlockHyperlinkBehavior.cs" />
<Compile Include="Common\Vector3Converter.cs" />
<Compile Include="SamplePages\AutoFocusBehavior\AutoFocusBehaviorPage.xaml.cs">
<DependentUpon>AutoFocusBehaviorPage.xaml</DependentUpon>
</Compile>
@ -507,6 +508,9 @@
<Compile Include="SamplePages\MetadataControl\MetadataControlPage.xaml.cs">
<DependentUpon>MetadataControlPage.xaml</DependentUpon>
</Compile>
<Compile Include="SamplePages\Shadows\AttachedDropShadowPage.xaml.cs">
<DependentUpon>AttachedDropShadowPage.xaml</DependentUpon>
</Compile>
<Compile Include="SamplePages\RichSuggestBox\RichSuggestBoxPage.xaml.cs">
<DependentUpon>RichSuggestBoxPage.xaml</DependentUpon>
</Compile>
@ -631,6 +635,9 @@
<Content Include="SamplePages\Primitives\ConstrainedBox.bind">
<SubType>Designer</SubType>
</Content>
<Content Include="SamplePages\Shadows\AttachedShadowWin2DXaml.bind" />
<Content Include="SamplePages\Shadows\AttachedShadowCompositionXaml.bind" />
<Content Include="SamplePages\Animations\Shadows\AnimatedCardShadowXaml.bind" />
<Content Include="SamplePages\KeyDownTriggerBehavior\KeyDownTriggerBehaviorXaml.bind" />
<Content Include="SamplePages\RichSuggestBox\RichSuggestBoxCode.bind" />
</ItemGroup>
@ -669,7 +676,6 @@
<Compile Include="SamplePages\UniformGrid\UniformGridPage.xaml.cs">
<DependentUpon>UniformGridPage.xaml</DependentUpon>
</Compile>
<Compile Include="Models\PropertyDescriptor\ThicknessPropertyOptions.cs" />
<Compile Include="Models\ThemeChangedArgs.cs" />
<Compile Include="Pages\SampleController.xaml.cs">
<DependentUpon>SampleController.xaml</DependentUpon>
@ -815,9 +821,6 @@
<Compile Include="SamplePages\DispatcherQueueHelper\DispatcherQueueHelperPage.xaml.cs">
<DependentUpon>DispatcherQueueHelperPage.xaml</DependentUpon>
</Compile>
<Compile Include="SamplePages\DropShadowPanel\DropShadowPanelPage.xaml.cs">
<DependentUpon>DropShadowPanelPage.xaml</DependentUpon>
</Compile>
<Compile Include="SamplePages\Expander\ExpanderPage.xaml.cs">
<DependentUpon>ExpanderPage.xaml</DependentUpon>
</Compile>
@ -992,6 +995,10 @@
<Content Include="SamplePages\Primitives\SwitchPresenter.bind">
<SubType>Designer</SubType>
</Content>
<Page Include="SamplePages\Shadows\AttachedDropShadowPage.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Content Include="SamplePages\RichSuggestBox\RichSuggestBoxXaml.bind">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -1287,10 +1294,6 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="SamplePages\DropShadowPanel\DropShadowPanelPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="SamplePages\GridSplitter\GridSplitterPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -1559,4 +1562,4 @@
</Target>
<!-- No-op to avoid build error when packing solution from commandline -->
<Target Name="Pack" />
</Project>
</Project>

Просмотреть файл

@ -13,6 +13,7 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.Models
Bool,
Brush,
TimeSpan,
Thickness
Thickness,
Vector3,
}
}

Просмотреть файл

@ -418,8 +418,12 @@ namespace Microsoft.Toolkit.Uwp.SampleApp
{
if (proxy[option.Name] is ValueHolder value)
{
var newString = value.Value is Windows.UI.Xaml.Media.SolidColorBrush brush ?
brush.Color.ToString() : value.Value.ToString();
var newString = value.Value switch
{
Windows.UI.Xaml.Media.SolidColorBrush brush => brush.Color.ToString(),
System.Numerics.Vector3 vector => vector.ToString().TrimStart('<').Replace(" ", string.Empty).TrimEnd('>'),
_ => value.Value.ToString()
};
result = result.Replace(option.OriginalString, newString);
result = result.Replace("@[" + option.Label + "]@", newString);
@ -630,12 +634,27 @@ namespace Microsoft.Toolkit.Uwp.SampleApp
case PropertyKind.Thickness:
try
{
var thicknessOptions = new ThicknessPropertyOptions { DefaultValue = value };
var thicknessOptions = new PropertyOptions { DefaultValue = value };
options = thicknessOptions;
}
catch (Exception ex)
{
Debug.WriteLine($"Unable to extract slider info from {value}({ex.Message})");
Debug.WriteLine($"Unable to extract thickness info from {value}({ex.Message})");
TrackingManager.TrackException(ex);
continue;
}
break;
case PropertyKind.Vector3:
try
{
var vector3Options = new PropertyOptions { DefaultValue = value };
options = vector3Options;
}
catch (Exception ex)
{
Debug.WriteLine($"Unable to extract vector3 info from {value}({ex.Message})");
TrackingManager.TrackException(ex);
continue;
}

Просмотреть файл

@ -60,7 +60,8 @@ namespace Microsoft.Toolkit.Uwp.SampleApp
{
allCategories = await JsonSerializer.DeserializeAsync<List<SampleCategory>>(jsonStream.AsStream(), new JsonSerializerOptions
{
ReadCommentHandling = JsonCommentHandling.Skip
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true,
});
}

Просмотреть файл

@ -7,7 +7,6 @@
xmlns:interactions="using:Microsoft.Xaml.Interactions.Core"
xmlns:ani="using:Microsoft.Toolkit.Uwp.UI.Animations"
xmlns:behaviors="using:Microsoft.Toolkit.Uwp.UI.Behaviors"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
mc:Ignorable="d">
<Button Background="Gray" Width="200" Height="200" HorizontalAlignment="Center" VerticalAlignment="Center">

Просмотреть файл

@ -0,0 +1,75 @@
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:Microsoft.Toolkit.Uwp.UI"
xmlns:media="using:Microsoft.Toolkit.Uwp.UI.Media"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:interactions="using:Microsoft.Xaml.Interactions.Core"
xmlns:ani="using:Microsoft.Toolkit.Uwp.UI.Animations"
xmlns:behaviors="using:Microsoft.Toolkit.Uwp.UI.Behaviors"
mc:Ignorable="d">
<Page.Resources>
<media:AttachedCardShadow x:Key="CommonShadow" Offset="4" CornerRadius="0"/>
<ani:AnimationSet x:Key="ShadowEnterAnimation">
<ani:OffsetDropShadowAnimation To="12"/>
</ani:AnimationSet>
<ani:AnimationSet x:Key="ShadowExitAnimation">
<ani:OffsetDropShadowAnimation To="4"/>
</ani:AnimationSet>
<ani:AnimationSet x:Key="ShadowPopAnimation" IsSequential="True">
<ani:TranslationAnimation To="-8" Duration="0:0:1"/>
<ani:OffsetDropShadowAnimation To="16" Duration="0:0:2" Target="{StaticResource CommonShadow}"/>
<ani:OffsetDropShadowAnimation To="4" Delay="0:0:0.5" Duration="0:0:2" Target="{StaticResource CommonShadow}"/>
<ani:TranslationAnimation To="0" Duration="0:0:1"/>
</ani:AnimationSet>
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image ui:Effects.Shadow="{StaticResource CommonShadow}"
Height="100" Width="100"
Source="ms-appx:///Assets/Photos/Owl.jpg">
<interactivity:Interaction.Behaviors>
<interactions:EventTriggerBehavior EventName="PointerEntered">
<behaviors:StartAnimationAction Animation="{StaticResource ShadowEnterAnimation}"/>
</interactions:EventTriggerBehavior>
<interactions:EventTriggerBehavior EventName="PointerExited">
<behaviors:StartAnimationAction Animation="{StaticResource ShadowExitAnimation}"/>
</interactions:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Image>
<Image ui:Effects.Shadow="{StaticResource CommonShadow}"
Height="100" Width="100"
Grid.Column="1"
Source="ms-appx:///Assets/Photos/Owl.jpg">
<interactivity:Interaction.Behaviors>
<interactions:EventTriggerBehavior EventName="PointerEntered">
<behaviors:StartAnimationAction Animation="{StaticResource ShadowEnterAnimation}"/>
</interactions:EventTriggerBehavior>
<interactions:EventTriggerBehavior EventName="PointerExited">
<behaviors:StartAnimationAction Animation="{StaticResource ShadowExitAnimation}"/>
</interactions:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Image>
<Button Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Center" VerticalAlignment="Top" Content="Click Me">
<interactivity:Interaction.Behaviors>
<interactions:EventTriggerBehavior EventName="Click">
<behaviors:StartAnimationAction Animation="{StaticResource ShadowPopAnimation}"/>
</interactions:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
</Grid>
</Page>

Просмотреть файл

@ -1,26 +0,0 @@
<Page x:Class="Microsoft.Toolkit.Uwp.SampleApp.SamplePages.DropShadowPanelPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<Style x:Key="TitleText"
TargetType="TextBlock">
<Setter Property="Margin" Value="5,20,0,5" />
<Setter Property="FontSize" Value="16" />
<Setter Property="Foreground" Value="{ThemeResource Brush-Grey-03}" />
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
<Style x:Key="DividingBorder"
TargetType="Border">
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="Margin" Value="0,0,0,20" />
</Style>
</Page.Resources>
<Grid x:Name="XamlRoot"/>
</Page>

Просмотреть файл

@ -1,22 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Windows.UI.Xaml.Controls;
namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
{
/// <summary>
/// A page that shows how to use the DropShadowPanel control.
/// </summary>
public sealed partial class DropShadowPanelPage : Page
{
/// <summary>
/// Initializes a new instance of the <see cref="DropShadowPanelPage"/> class.
/// </summary>
public DropShadowPanelPage()
{
InitializeComponent();
}
}
}

Просмотреть файл

@ -0,0 +1,9 @@
<Page x:Class="Microsoft.Toolkit.Uwp.SampleApp.SamplePages.AttachedDropShadowPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<!-- Shallow Copy in XamlOnlyPage -->
</Page>

Просмотреть файл

@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Toolkit.Uwp.UI;
using Windows.UI.Xaml;
namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
{
public sealed partial class AttachedDropShadowPage : IXamlRenderListener
{
public AttachedDropShadowPage()
{
InitializeComponent();
}
public void OnXamlRendered(FrameworkElement control)
{
// This is done as we don't have x:Bind in live xaml, so we find and attach after.
var castToTarget = control.FindChild("ShadowTarget");
if (castToTarget != null)
{
if (control.Resources.TryGetValue("CommonShadow", out var resource) && resource is AttachedDropShadow shadow)
{
shadow.CastTo = castToTarget;
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,85 @@
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:Microsoft.Toolkit.Uwp.UI"
mc:Ignorable="d">
<Page.Resources>
<ui:AttachedDropShadow x:Key="CommonShadow" Offset="4"/><!-- CastTo="{x:Bind ShadowTarget}"/>-->
<Style TargetType="Button" BasedOn="{StaticResource DefaultButtonStyle}">
<Setter Property="ui:Effects.Shadow" Value="{StaticResource CommonShadow}"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<!-- We set a solid color for the background button otherwise the shadow bleeds through as
the new style is transparent. See AttachedCardShadow for proper element clipping. -->
<Setter Property="Background" Value="Red"/>
</Style>
</Page.Resources>
<ScrollViewer>
<Grid>
<!-- The ShadowTarget Grid here is a *sibling* element behind where our elements which will cast
shadows are located, this is important as otherwise if we used a parent element the
shadows would appear on top of our elements instead!
It is also placed within the ScrollViewer here so shadows move with their elements. -->
<Grid x:Name="ShadowTarget"/>
<StackPanel Spacing="32" VerticalAlignment="Center">
<!-- All buttons on this page have the shadow from the common style!
The Shadow definition is Shared! -->
<Button Content="I Have a Shadow!"/>
<!-- Can apply the same shadow to any type of element! -->
<Image ui:Effects.Shadow="{StaticResource CommonShadow}"
Height="100" Width="100"
Source="ms-appx:///Assets/Photos/Owl.jpg"/>
<!-- You can still apply a Shadow directly and even use binding with it to manipulate at runtime! -->
<Rectangle RadiusX="32" RadiusY="32"
Height="100" Width="100"
Stroke="Blue" StrokeThickness="1">
<Rectangle.Fill>
<ImageBrush ImageSource="ms-appx:///Assets/Photos/Owl.jpg"/>
</Rectangle.Fill>
<ui:Effects.Shadow>
<ui:AttachedDropShadow BlurRadius="@[BlurRadius:DoubleSlider:8.0:0.0-10.0]"
CornerRadius="32"
Color="@[Color:Brush:Black]"
Offset="@[Offset:Vector3:4,4]"
Opacity="@[Opacity:DoubleSlider:1.0:0.0-1.0]"
CastTo="{Binding ElementName=ShadowTarget}"/>
</ui:Effects.Shadow>
</Rectangle>
<!-- This particular scenario of attaching directly to a raw element is easier than the Win2D equivelent. -->
<Border Height="100" Width="100"
CornerRadius="32"
BorderBrush="White" BorderThickness="1">
<Border.Background>
<ImageBrush ImageSource="ms-appx:///Assets/Photos/Owl.jpg"/>
</Border.Background>
<ui:Effects.Shadow>
<ui:AttachedDropShadow CornerRadius="32"
Offset="4,4"
CastTo="{Binding ElementName=ShadowTarget}"/>
</ui:Effects.Shadow>
</Border>
<!-- Note how even though this element is transparent, the shadow still shows through,
to have this not occur use the AttachedCardShadow. -->
<Rectangle ui:Effects.Shadow="{StaticResource CommonShadow}"
Fill="#80FF0000"
RadiusX="4" RadiusY="4"
Width="200" Height="100"/>
<!-- This is the same behavior as the old DropShadowPanel where the shadow bleeds through and
the Shadow opacity is tied to the Rectangle itself -->
<controls:DropShadowPanel xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
OffsetX="4" OffsetY="4"
BlurRadius="12"
HorizontalAlignment="Center">
<Rectangle Fill="#80FF0000"
RadiusX="4" RadiusY="4"
Width="200" Height="100"/>
</controls:DropShadowPanel>
<Button Content="I Also have a Shadow!"/>
</StackPanel>
</Grid>
</ScrollViewer>
</Page>

Просмотреть файл

@ -0,0 +1,80 @@
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:Microsoft.Toolkit.Uwp.UI"
xmlns:media="using:Microsoft.Toolkit.Uwp.UI.Media"
mc:Ignorable="d">
<!-- TODO: Animation-->
<Page.Resources>
<media:AttachedCardShadow x:Key="CommonShadow" Offset="4"/>
<Style TargetType="Button" BasedOn="{StaticResource DefaultButtonStyle}">
<Setter Property="ui:Effects.Shadow" Value="{StaticResource CommonShadow}"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</Page.Resources>
<ScrollViewer>
<StackPanel Spacing="32" VerticalAlignment="Center">
<!-- All buttons on this page have the shadow from the common style!
The Shadow definition is Shared! -->
<Button Content="I Have a Shadow!"/>
<!-- Can apply the same shadow to any type of element! -->
<Image ui:Effects.Shadow="{StaticResource CommonShadow}"
Height="100" Width="100"
Source="ms-appx:///Assets/Photos/Owl.jpg"/>
<!-- You can still apply a Shadow directly and even use binding with it to manipulate at runtime! -->
<Rectangle RadiusX="32" RadiusY="32"
Height="100" Width="100"
Stroke="Blue" StrokeThickness="1">
<Rectangle.Fill>
<ImageBrush ImageSource="ms-appx:///Assets/Photos/Owl.jpg"/>
</Rectangle.Fill>
<ui:Effects.Shadow>
<media:AttachedCardShadow BlurRadius="@[BlurRadius:DoubleSlider:8.0:0.0-10.0]"
CornerRadius="32"
Color="@[Color:Brush:Black]"
Offset="@[Offset:Vector3:4,4]"
Opacity="@[Opacity:DoubleSlider:1.0:0.0-1.0]"/>
</ui:Effects.Shadow>
</Rectangle>
<!-- If you want to apply a shadow directly in your visual tree to an untemplated element
You need to at least have a layer of depth as seen in this next example. -->
<Border Height="100" Width="100">
<Border CornerRadius="32"
BorderBrush="White" BorderThickness="1">
<Border.Background>
<ImageBrush ImageSource="ms-appx:///Assets/Photos/Owl.jpg"/>
</Border.Background>
</Border>
<!-- We need to put the Shadow on a parent element here as otherwise the
rounding of the border of the image above clips the shadow itself.
This is easier to perform with the Composition only based shadow as the
Shadow is projected to another element. -->
<ui:Effects.Shadow>
<media:AttachedCardShadow CornerRadius="32"
Offset="4,4"/>
</ui:Effects.Shadow>
</Border>
<!-- Note how even though this element is transparent, the shadow only shows on its edges still! -->
<Rectangle ui:Effects.Shadow="{StaticResource CommonShadow}"
Fill="#80FF0000"
RadiusX="4" RadiusY="4"
Width="200" Height="100"/>
<!-- Compared to the old DropShadowPanel where the shadow bleeds through and
the Shadow opacity is tied to the Rectangle itself -->
<controls:DropShadowPanel xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
OffsetX="4" OffsetY="4"
BlurRadius="12"
HorizontalAlignment="Center">
<Rectangle Fill="#80FF0000"
RadiusX="4" RadiusY="4"
Width="200" Height="100"/>
</controls:DropShadowPanel>
<Button Content="I Also have a Shadow!"/>
</StackPanel>
</ScrollViewer>
</Page>

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 2.1 KiB

После

Ширина:  |  Высота:  |  Размер: 2.1 KiB

Просмотреть файл

@ -88,7 +88,7 @@
VerticalAlignment="Center"
HorizontalAlignment="Center"
IsMasked="@[Is Masked]">
<Image Width="200" Source="/SamplePages/DropShadowPanel/Unicorn.png" Stretch="Uniform" />
<Image Width="200" Source="/SamplePages/Shadows/Unicorn.png" Stretch="Uniform" />
</controls:DropShadowPanel>
<controls:DropShadowPanel BlurRadius="@[BlurRadius]"
ShadowOpacity="@[Opacity]"
@ -98,7 +98,7 @@
VerticalAlignment="Center"
HorizontalAlignment="Center"
IsMasked="@[Is Masked]">
<Image Width="200" Source="/SamplePages/DropShadowPanel/Trex.png" Stretch="Uniform" />
<Image Width="200" Source="/SamplePages/Shadows/Trex.png" Stretch="Uniform" />
</controls:DropShadowPanel>
</StackPanel>
</StackPanel>

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 5.6 KiB

После

Ширина:  |  Высота:  |  Размер: 5.6 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 6.1 KiB

После

Ширина:  |  Высота:  |  Размер: 6.1 KiB

Просмотреть файл

@ -50,6 +50,10 @@
</controls:CaseCollection>
</controls:SwitchPresenter>
<controls:ConstrainedBox x:Key="ConstrainedBoxControl" />
<media:AttachedCardShadow x:Key="AttachedShadow" />
<ui:AttachedDropShadow x:Key="AttachedDropShadow" />
<controls:DropShadowPanel x:Key="DropShadowPanel"
ui:Effects.Shadow="{StaticResource AttachedShadow}" />
</Page.Resources>
<Grid>
@ -64,6 +68,10 @@
<ani:SaturationEffectAnimation />
<ani:AnimationScope />
<ani:ExposureEffectAnimation />
<ani:BlurRadiusDropShadowAnimation />
<ani:ColorDropShadowAnimation />
<ani:OffsetDropShadowAnimation />
<ani:OpacityDropShadowAnimation />
</ani:AnimationSet>
</ani:Explicit.Animations>
<media:UIElementExtensions.VisualFactory>

Просмотреть файл

@ -126,6 +126,8 @@
{
"Name": "RadialProgressBar",
"Type": "RadialProgressBarPage",
"BadgeUpdateVersionRequired": "DEPRECATED",
"DeprecatedWarning": "Please migrate to the ProgressRing control from WinUI with IsIndeterminate set to false, this control will be removed in a future release. https://aka.ms/winui",
"Subcategory": "Status and Info",
"About": "The radial progress bar displays progress as a circle getting filled.",
"CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Controls.Core/RadialProgressBar",
@ -168,7 +170,7 @@
"Type": "ScrollHeaderPage",
"Subcategory": "Layout",
"About": "A UI control that works as a ListView or GridView header control with quick return, sticky and fade behavior.",
"CodeUrl" : "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Behaviors/Headers",
"CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Behaviors/Headers",
"XamlCodeFile": "ScrollHeaderCode.bind",
"Icon": "/SamplePages/ScrollHeader/ScrollHeader.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/behaviors/HeaderBehaviors.md"
@ -183,15 +185,36 @@
"Icon": "/SamplePages/GridSplitter/GridSplitter.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/GridSplitter.md"
},
{
"Name": "AttachedDropShadow (Composition)",
"Type": "AttachedDropShadowPage",
"Subcategory": "Media",
"About": "An AttachedDropShadow allows the creation of a DropShadow for any Xaml FrameworkElement in markup.",
"CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI/Shadows",
"XamlCodeFile": "/SamplePages/Shadows/AttachedShadowCompositionXaml.bind",
"Icon": "/SamplePages/Shadows/DropShadowPanel.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/DropShadowPanel.md"
},
{
"Name": "AttachedCardShadow (Win2D)",
"Subcategory": "Media",
"About": "An AttachedCardShadow allows the creation of a DropShadow for any Xaml FrameworkElement in markup.",
"CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Media/Shadows",
"XamlCodeFile": "/SamplePages/Shadows/AttachedShadowWin2DXaml.bind",
"Icon": "/SamplePages/Shadows/DropShadowPanel.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/DropShadowPanel.md",
"BadgeUpdateVersionRequired": "May 2019 update required",
"ApiCheck": "Windows.UI.Composition.CompositionVisualSurface"
},
{
"Name": "DropShadowPanel",
"Type": "DropShadowPanelPage",
"Subcategory": "Media",
"About": "DropShadowPanel contol allows the creation of a DropShadow for any Xaml FrameworkElement in markup.",
"BadgeUpdateVersionRequired": "DEPRECATED",
"DeprecatedWarning": "This control will be removed in a future major release. Please use the AttachedDropShadow or AttachedCardShadow extensions instead.",
"CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Controls.Core/DropShadowPanel",
"XamlCodeFile": "DropShadowPanelXaml.bind",
"Icon": "/SamplePages/DropShadowPanel/DropShadowPanel.png",
"BadgeUpdateVersionRequired": "Anniversary Update required",
"XamlCodeFile": "/SamplePages/Shadows/DropShadowPanelXaml.bind",
"Icon": "/SamplePages/Shadows/DropShadowPanel.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/DropShadowPanel.md"
},
{
@ -359,7 +382,6 @@
"XamlCodeFile": "InfiniteCanvas.bind",
"Icon": "/SamplePages/InfiniteCanvas/InfiniteCanvas.png",
"ApiCheck": "Windows.UI.Xaml.Controls.ColorPicker",
"BadgeUpdateVersionRequired": "Fall Creators Update required",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/InfiniteCanvas.md"
},
{
@ -570,7 +592,6 @@
"CodeFile": "/SamplePages/Animations/Behaviors/BlurBehaviorCode.bind",
"XamlCodeFile": "/SamplePages/Animations/Behaviors/BlurBehaviorXaml.bind",
"Icon": "/SamplePages/Animations/Behaviors/BlurBehavior.png",
"BadgeUpdateVersionRequired": "Anniversary Update required",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/brushes/PipelineVisualFactory.md"
},
{
@ -581,7 +602,6 @@
"CodeFile": "/SamplePages/Animations/Behaviors/SaturationBehaviorCode.bind",
"XamlCodeFile": "/SamplePages/Animations/Behaviors/SaturationBehaviorXaml.bind",
"Icon": "/SamplePages/Animations/Behaviors/SaturationBehavior.png",
"BadgeUpdateVersionRequired": "Anniversary Update required",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/brushes/PipelineVisualFactory.md"
},
{
@ -611,7 +631,6 @@
"CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Animations",
"XamlCodeFile": "ItemsReorderAnimation.bind",
"Icon": "/SamplePages/ItemsReorderAnimation/ItemsReorderAnimation.png",
"BadgeUpdateVersionRequired": "Anniversary Update required",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/ItemsReorderAnimation.md"
},
{
@ -631,7 +650,6 @@
"CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Animations",
"Icon": "/SamplePages/Implicit Animations/ImplicitAnimations.png",
"XamlCodeFile": "ImplicitAnimationsCode.bind",
"BadgeUpdateVersionRequired": "Creators Update required",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/ImplicitAnimationSet.md"
},
{
@ -643,7 +661,15 @@
"XamlCodeFile": "ConnectedAnimationsCode.bind",
"DisableXamlEditorRendering": true,
"Icon": "/SamplePages/Connected Animations/ConnectedAnimations.png",
"BadgeUpdateVersionRequired": "Creators Update required",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/ConnectedAnimations.md"
},
{
"Name": "Shadow Animations",
"Subcategory": "Effect",
"About": "Example of animating an Attached Shadow",
"CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Shadows",
"XamlCodeFile": "/SamplePages/Animations/Shadows/AnimatedCardShadowXaml.bind",
"Icon": "/SamplePages/Shadows/DropShadowPanel.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/ConnectedAnimations.md"
}
]
@ -726,8 +752,7 @@
"CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper",
"CodeFile": "BluetoothLEHelperCode.bind",
"Icon": "/SamplePages/BluetoothLEHelper/BluetoothLEHelper.png",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/helpers/BluetoothLEHelper.md",
"BadgeUpdateVersionRequired": "Creators Update required"
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/helpers/BluetoothLEHelper.md"
},
{
"Name": "SystemInformation",
@ -1190,7 +1215,6 @@
"CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI/Extensions/TextBox/TextBoxExtensions.SurfaceDial.cs",
"XamlCodeFile": "/SamplePages/SurfaceDialTextbox/SurfaceDialTextboxCode.bind",
"Icon": "/SamplePages/SurfaceDialTextbox/SurfaceDialTextbox.png",
"BadgeUpdateVersionRequired": "Anniversary Update required",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/extensions/SurfaceDialTextboxHelper.md"
},
{
@ -1200,7 +1224,6 @@
"CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI/Extensions/VisualExtensions.cs",
"XamlCodeFile": "VisualExtensionsCode.bind",
"Icon": "/SamplePages/Visual Extensions/VisualExtensions.png",
"BadgeUpdateVersionRequired": "Creators Update required",
"DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/extensions/VisualExtensions.md"
},
{

Просмотреть файл

@ -4,4 +4,5 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Toolkit.Uwp.UI.Media")]
[assembly: InternalsVisibleTo("Microsoft.Toolkit.Uwp.UI.Behaviors")]
[assembly: InternalsVisibleTo("Microsoft.Toolkit.Uwp.UI.Media")]

Просмотреть файл

@ -0,0 +1,112 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media.Animation;
using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions;
#nullable enable
namespace Microsoft.Toolkit.Uwp.UI.Animations
{
/// <summary>
/// A custom animation targeting a property on an <see cref="IAttachedShadow"/> instance.
/// </summary>
/// <typeparam name="TValue">
/// The type to use for the public <see cref="Animation{TValue,TKeyFrame}.To"/> and <see cref="Animation{TValue,TKeyFrame}.From"/>
/// properties. This can differ from <typeparamref name="TKeyFrame"/> to facilitate XAML parsing.
/// </typeparam>
/// <typeparam name="TKeyFrame">The actual type of keyframe values in use.</typeparam>
public abstract class ShadowAnimation<TValue, TKeyFrame> : Animation<TValue, TKeyFrame>, IAttachedTimeline
where TKeyFrame : unmanaged
{
/// <summary>
/// Gets or sets the linked <see cref="IAttachedShadow"/> instance to animate.
/// </summary>
public IAttachedShadow? Target
{
get => (IAttachedShadow?)GetValue(TargetProperty);
set => SetValue(TargetProperty, value);
}
/// <summary>
/// Identifies the <seealso cref="Target"/> dependency property.
/// </summary>
public static readonly DependencyProperty TargetProperty = DependencyProperty.Register(
nameof(Target),
typeof(IAttachedShadow),
typeof(ShadowAnimation<TValue, TKeyFrame>),
new PropertyMetadata(null));
/// <inheritdoc/>
public override AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint)
{
throw new NotSupportedException();
}
/// <inheritdoc/>
public AnimationBuilder AppendToBuilder(AnimationBuilder builder, UIElement parent, TimeSpan? delayHint = null, TimeSpan? durationHint = null, EasingType? easingTypeHint = null, EasingMode? easingModeHint = null)
{
if (ExplicitTarget is not string explicitTarget)
{
static AnimationBuilder ThrowArgumentNullException()
{
throw new ArgumentNullException(
"The target shadow cannot be animated at this time.");
}
return ThrowArgumentNullException();
}
if (Target is IAttachedShadow allShadows)
{
// in this case we'll animate all the shadows being used.
foreach (var context in allShadows.EnumerateElementContexts()) //// TODO: Find better way!!!
{
NormalizedKeyFrameAnimationBuilder<TKeyFrame>.Composition keyFrameBuilder = new(
explicitTarget,
Delay ?? delayHint ?? DefaultDelay,
Duration ?? durationHint ?? DefaultDuration,
Repeat,
DelayBehavior);
AppendToBuilder(keyFrameBuilder, easingTypeHint, easingModeHint);
CompositionAnimation animation = keyFrameBuilder.GetAnimation(context.Shadow, out _);
builder.ExternalAnimation(context.Shadow, animation);
}
return builder;
}
else
{
var shadowBase = Effects.GetShadow(parent as FrameworkElement);
if (shadowBase == null)
{
static AnimationBuilder ThrowArgumentNullException() => throw new ArgumentNullException("The target's shadow is null, make sure to set the Target property to an element with a Shadow");
return ThrowArgumentNullException();
}
var shadow = shadowBase.GetElementContext((FrameworkElement)parent).Shadow;
NormalizedKeyFrameAnimationBuilder<TKeyFrame>.Composition keyFrameBuilder = new(
explicitTarget,
Delay ?? delayHint ?? DefaultDelay,
Duration ?? durationHint ?? DefaultDuration,
Repeat,
DelayBehavior);
AppendToBuilder(keyFrameBuilder, easingTypeHint, easingModeHint);
CompositionAnimation animation = keyFrameBuilder.GetAnimation(shadow, out _);
return builder.ExternalAnimation(shadow, animation);
}
}
}
}

Просмотреть файл

@ -119,7 +119,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
{
foreach (object node in this)
{
if (node is ITimeline timeline)
if (node is IAttachedTimeline attachedTimeline)
{
var builder = AnimationBuilder.Create();
attachedTimeline.AppendToBuilder(builder, element);
await builder.StartAsync(element, token);
}
else if (node is ITimeline timeline)
{
var builder = AnimationBuilder.Create();
@ -166,6 +174,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
{
switch (node)
{
case IAttachedTimeline attachedTimeline:
builder = attachedTimeline.AppendToBuilder(builder, element);
break;
case ITimeline timeline:
builder = timeline.AppendToBuilder(builder);
break;

Просмотреть файл

@ -0,0 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media.Animation;
namespace Microsoft.Toolkit.Uwp.UI.Animations
{
/// <summary>
/// An interface representing a XAML model for a custom animation that requires a specific parent <see cref="UIElement"/> context.
/// </summary>
public interface IAttachedTimeline
{
/// <summary>
/// Appends the current animation to a target <see cref="AnimationBuilder"/> instance.
/// This method is used when the current <see cref="ITimeline"/> instance is explicitly triggered.
/// </summary>
/// <param name="builder">The target <see cref="AnimationBuilder"/> instance to schedule the animation on.</param>
/// <param name="parent">The parent <see cref="UIElement"/> this animation will be started on.</param>
/// <param name="delayHint">A hint for the animation delay, if present.</param>
/// <param name="durationHint">A hint for the animation duration, if present.</param>
/// <param name="easingTypeHint">A hint for the easing type, if present.</param>
/// <param name="easingModeHint">A hint for the easing mode, if present.</param>
/// <returns>The same <see cref="AnimationBuilder"/> instance as <paramref name="builder"/>.</returns>
AnimationBuilder AppendToBuilder(
AnimationBuilder builder,
UIElement parent,
TimeSpan? delayHint = null,
TimeSpan? durationHint = null,
EasingType? easingTypeHint = null,
EasingMode? easingModeHint = null);
}
}

Просмотреть файл

@ -13,7 +13,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
public interface ITimeline
{
/// <summary>
/// Appens the current animation to a target <see cref="AnimationBuilder"/> instance.
/// Appends the current animation to a target <see cref="AnimationBuilder"/> instance.
/// This method is used when the current <see cref="ITimeline"/> instance is explicitly triggered.
/// </summary>
/// <param name="builder">The target <see cref="AnimationBuilder"/> instance to schedule the animation on.</param>

Просмотреть файл

@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Windows.UI.Composition;
namespace Microsoft.Toolkit.Uwp.UI.Animations
{
/// <summary>
/// A blur radius animation working on the composition layer.
/// </summary>
public sealed class BlurRadiusDropShadowAnimation : ShadowAnimation<double?, double>
{
/// <inheritdoc/>
protected override string ExplicitTarget => nameof(DropShadow.BlurRadius);
/// <inheritdoc/>
protected override (double?, double?) GetParsedValues()
{
return (To, From);
}
}
}

Просмотреть файл

@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Windows.UI;
using Windows.UI.Composition;
#pragma warning disable CS0419
namespace Microsoft.Toolkit.Uwp.UI.Animations
{
/// <summary>
/// A custom <see cref="Color"/> animation on a <see cref="DropShadow"/>.
/// </summary>
public sealed class ColorDropShadowAnimation : ShadowAnimation<Color?, Color>
{
/// <inheritdoc/>
protected override string ExplicitTarget => nameof(DropShadow.Color);
/// <inheritdoc/>
protected override (Color?, Color?) GetParsedValues()
{
return (To, From);
}
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Numerics;
using Windows.UI.Composition;
namespace Microsoft.Toolkit.Uwp.UI.Animations
{
/// <summary>
/// An offset animation working on the composition layer.
/// </summary>
public sealed class OffsetDropShadowAnimation : ShadowAnimation<string, Vector3>
{
/// <inheritdoc/>
protected override string ExplicitTarget => nameof(DropShadow.Offset);
/// <inheritdoc/>
protected override (Vector3?, Vector3?) GetParsedValues()
{
return (To?.ToVector3(), From?.ToVector3());
}
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Windows.UI.Composition;
namespace Microsoft.Toolkit.Uwp.UI.Animations
{
/// <summary>
/// An opacity animation working on the composition layer.
/// </summary>
public sealed class OpacityDropShadowAnimation : ShadowAnimation<double?, double>
{
/// <inheritdoc/>
protected override string ExplicitTarget => nameof(DropShadow.Opacity);
/// <inheritdoc/>
protected override (double?, double?) GetParsedValues()
{
return (To, From);
}
}
}

Просмотреть файл

@ -58,13 +58,19 @@ namespace Microsoft.Toolkit.Uwp.UI.Behaviors
ThrowArgumentNullException();
}
UIElement parent = null;
if (TargetObject is not null)
{
Animation.Start(TargetObject);
}
else if (Animation.ParentReference?.TryGetTarget(out parent) == true) //// TODO: Tidy... apply same pattern to Activities?
{
Animation.Start(parent);
}
else
{
Animation.Start();
Animation.Start(sender as UIElement);
}
return null!;

Просмотреть файл

@ -58,13 +58,19 @@ namespace Microsoft.Toolkit.Uwp.UI.Behaviors
ThrowArgumentNullException();
}
UIElement parent = null;
if (TargetObject is not null)
{
Animation.Stop(TargetObject);
}
else if (Animation.ParentReference?.TryGetTarget(out parent) == true) //// TODO: Tidy...
{
Animation.Stop(parent);
}
else
{
Animation.Stop();
Animation.Stop(sender as UIElement);
}
return null!;

Просмотреть файл

@ -17,6 +17,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// The <see cref="DropShadowPanel"/> control allows the creation of a DropShadow for any Xaml FrameworkElement in markup
/// making it easier to add shadows to Xaml without having to directly drop down to Windows.UI.Composition APIs.
/// </summary>
[Obsolete("DropShadowPanel will be removed in a future release, please use the AttachedDropShadow or AttachedCardShadow implementations instead.")]
[TemplatePart(Name = PartShadow, Type = typeof(Border))]
public partial class DropShadowPanel : ContentControl
{
@ -171,7 +172,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
// alpha mask even if Content happens to extend any of the other classes
if (Content is IAlphaMaskProvider maskedControl)
{
mask = maskedControl.GetAlphaMask();
if (maskedControl.WaitUntilLoaded && maskedControl is FrameworkElement element && !element.IsLoaded)
{
element.Loaded += CustomMaskedElement_Loaded;
}
else
{
mask = maskedControl.GetAlphaMask();
}
}
else if (Content is Image)
{
@ -185,17 +193,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
{
mask = ((TextBlock)Content).GetAlphaMask();
}
else if (Content is ImageExBase imageExBase)
{
imageExBase.ImageExInitialized += ImageExInitialized;
if (imageExBase.IsInitialized)
{
imageExBase.ImageExInitialized -= ImageExInitialized;
mask = ((ImageExBase)Content).GetAlphaMask();
}
}
_dropShadow.Mask = mask;
}
@ -205,15 +202,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
}
}
private void ImageExInitialized(object sender, EventArgs e)
private void CustomMaskedElement_Loaded(object sender, RoutedEventArgs e)
{
var imageExBase = (ImageExBase)Content;
if (sender is FrameworkElement element)
{
element.Loaded -= CustomMaskedElement_Loaded;
imageExBase.ImageExInitialized -= ImageExInitialized;
CompositionBrush mask = ((ImageExBase)Content).GetAlphaMask();
_dropShadow.Mask = mask;
_dropShadow.Mask = ((IAlphaMaskProvider)element).GetAlphaMask();
}
}
private void UpdateShadowOffset(float x, float y, float z)

Просмотреть файл

@ -19,7 +19,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
[TemplateVisualState(Name = UnloadedState, GroupName = CommonGroup)]
[TemplateVisualState(Name = FailedState, GroupName = CommonGroup)]
[TemplatePart(Name = PartImage, Type = typeof(object))]
public abstract partial class ImageExBase : Control
public abstract partial class ImageExBase : Control, IAlphaMaskProvider
{
private bool _isInViewport;
@ -58,6 +58,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// </summary>
protected object Image { get; private set; }
/// <inheritdoc/>
public bool WaitUntilLoaded => true;
/// <summary>
/// Initializes a new instance of the <see cref="ImageExBase"/> class.
/// </summary>

Просмотреть файл

@ -0,0 +1,175 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Numerics;
using Microsoft.Graphics.Canvas.Geometry;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Composition;
using Windows.UI.Xaml;
namespace Microsoft.Toolkit.Uwp.UI.Media
{
/// <summary>
/// A performant rectangular <see cref="DropShadow"/> which can be attached to any <see cref="FrameworkElement"/>. It uses Win2D to create a clipped area of the outline of the element such that transparent elements don't see the shadow below them, and the shadow can be attached without having to project to another surface. It is animatable, can be shared via a resource, and used in a <see cref="Style"/>.
/// </summary>
/// <remarks>
/// This shadow will not work on <see cref="FrameworkElement"/> which is directly clipping to its bounds (e.g. a <see cref="Windows.UI.Xaml.Controls.Border"/> using a <see cref="Windows.UI.Xaml.Controls.Control.CornerRadius"/>). An extra <see cref="Windows.UI.Xaml.Controls.Border"/> can instead be applied around the clipped border with the Shadow to create the desired effect. Most existing controls due to how they're templated will not encounter this behavior or require this workaround.
/// </remarks>
public sealed class AttachedCardShadow : AttachedShadowBase
{
private const float MaxBlurRadius = 72;
private static readonly TypedResourceKey<CompositionGeometricClip> ClipResourceKey = "Clip";
private static readonly TypedResourceKey<CompositionPathGeometry> PathGeometryResourceKey = "PathGeometry";
private static readonly TypedResourceKey<CompositionRoundedRectangleGeometry> RoundedRectangleGeometryResourceKey = "RoundedGeometry";
private static readonly TypedResourceKey<CompositionSpriteShape> ShapeResourceKey = "Shape";
private static readonly TypedResourceKey<ShapeVisual> ShapeVisualResourceKey = "ShapeVisual";
private static readonly TypedResourceKey<CompositionSurfaceBrush> SurfaceBrushResourceKey = "SurfaceBrush";
private static readonly TypedResourceKey<CompositionVisualSurface> VisualSurfaceResourceKey = "VisualSurface";
/// <summary>
/// The <see cref="DependencyProperty"/> for <see cref="CornerRadius"/>
/// </summary>
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register(
nameof(CornerRadius),
typeof(double),
typeof(AttachedCardShadow),
new PropertyMetadata(4d, OnDependencyPropertyChanged)); // Default WinUI ControlCornerRadius is 4
/// <summary>
/// Gets or sets the roundness of the shadow's corners.
/// </summary>
public double CornerRadius
{
get => (double)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
/// <inheritdoc/>
public override bool IsSupported => SupportsCompositionVisualSurface;
/// <inheritdoc/>
protected internal override bool SupportsOnSizeChangedEvent => true;
/// <inheritdoc/>
protected override void OnPropertyChanged(AttachedShadowElementContext context, DependencyProperty property, object oldValue, object newValue)
{
if (property == CornerRadiusProperty)
{
var geometry = context.GetResource(RoundedRectangleGeometryResourceKey);
if (geometry != null)
{
geometry.CornerRadius = new Vector2((float)(double)newValue);
}
UpdateShadowClip(context);
}
else
{
base.OnPropertyChanged(context, property, oldValue, newValue);
}
}
/// <inheritdoc/>
protected override CompositionBrush GetShadowMask(AttachedShadowElementContext context)
{
if (!SupportsCompositionVisualSurface)
{
return null;
}
// Create rounded rectangle geometry and add it to a shape
var geometry = context.GetResource(RoundedRectangleGeometryResourceKey) ?? context.AddResource(
RoundedRectangleGeometryResourceKey,
context.Compositor.CreateRoundedRectangleGeometry());
geometry.CornerRadius = new Vector2((float)CornerRadius);
var shape = context.GetResource(ShapeResourceKey) ?? context.AddResource(ShapeResourceKey, context.Compositor.CreateSpriteShape(geometry));
shape.FillBrush = context.Compositor.CreateColorBrush(Colors.Black);
// Create a ShapeVisual so that our geometry can be rendered to a visual
var shapeVisual = context.GetResource(ShapeVisualResourceKey) ??
context.AddResource(ShapeVisualResourceKey, context.Compositor.CreateShapeVisual());
shapeVisual.Shapes.Add(shape);
// Create a CompositionVisualSurface, which renders our ShapeVisual to a texture
var visualSurface = context.GetResource(VisualSurfaceResourceKey) ??
context.AddResource(VisualSurfaceResourceKey, context.Compositor.CreateVisualSurface());
visualSurface.SourceVisual = shapeVisual;
// Create a CompositionSurfaceBrush to render our CompositionVisualSurface to a brush.
// Now we have a rounded rectangle brush that can be used on as the mask for our shadow.
var surfaceBrush = context.GetResource(SurfaceBrushResourceKey) ?? context.AddResource(
SurfaceBrushResourceKey,
context.Compositor.CreateSurfaceBrush(visualSurface));
geometry.Size = visualSurface.SourceSize = shapeVisual.Size = context.Element.RenderSize.ToVector2();
return surfaceBrush;
}
/// <inheritdoc/>
protected override CompositionClip GetShadowClip(AttachedShadowElementContext context)
{
// The way this shadow works without the need to project on another element is because
// we're clipping the inner part of the shadow which would be cast on the element
// itself away. This method is creating an outline so that we are only showing the
// parts of the shadow that are outside the element's context.
// Note: This does cause an issue if the element does clip itself to its bounds, as then
// the shadowed area is clipped as well.
var pathGeom = context.GetResource(PathGeometryResourceKey) ??
context.AddResource(PathGeometryResourceKey, context.Compositor.CreatePathGeometry());
var clip = context.GetResource(ClipResourceKey) ?? context.AddResource(ClipResourceKey, context.Compositor.CreateGeometricClip(pathGeom));
// Create rounded rectangle geometry at a larger size that compensates for the size of the stroke,
// as we want the inside edge of the stroke to match the edges of the element.
// Additionally, the inside edge of the stroke will have a smaller radius than the radius we specified.
// Using "(StrokeThickness / 2) + Radius" as our rectangle's radius will give us an inside stroke radius that matches the radius we want.
var canvasRectangle = CanvasGeometry.CreateRoundedRectangle(
null,
-MaxBlurRadius / 2,
-MaxBlurRadius / 2,
(float)context.Element.ActualWidth + MaxBlurRadius,
(float)context.Element.ActualHeight + MaxBlurRadius,
(MaxBlurRadius / 2) + (float)CornerRadius,
(MaxBlurRadius / 2) + (float)CornerRadius);
var canvasStroke = canvasRectangle.Stroke(MaxBlurRadius);
pathGeom.Path = new CompositionPath(canvasStroke);
return clip;
}
/// <inheritdoc/>
protected internal override void OnSizeChanged(AttachedShadowElementContext context, Size newSize, Size previousSize)
{
var sizeAsVec2 = newSize.ToVector2();
var geometry = context.GetResource(RoundedRectangleGeometryResourceKey);
if (geometry != null)
{
geometry.Size = sizeAsVec2;
}
var visualSurface = context.GetResource(VisualSurfaceResourceKey);
if (geometry != null)
{
visualSurface.SourceSize = sizeAsVec2;
}
var shapeVisual = context.GetResource(ShapeVisualResourceKey);
if (geometry != null)
{
shapeVisual.Size = sizeAsVec2;
}
UpdateShadowClip(context);
base.OnSizeChanged(context, newSize, previousSize);
}
}
}

Просмотреть файл

@ -26,7 +26,11 @@ namespace Microsoft.Toolkit.Uwp.UI
[Pure]
public static Vector2 ToVector2(this string text)
{
if (text.Length > 0)
if (text.Length == 0)
{
return Vector2.Zero;
}
else
{
// The format <x> or <x, y> is supported
text = Unbracket(text);
@ -71,7 +75,11 @@ namespace Microsoft.Toolkit.Uwp.UI
[Pure]
public static Vector3 ToVector3(this string text)
{
if (text.Length > 0)
if (text.Length == 0)
{
return Vector3.Zero;
}
else
{
text = Unbracket(text);
@ -95,6 +103,10 @@ namespace Microsoft.Toolkit.Uwp.UI
return new(x, y, z);
}
}
else if (values.Length == 2)
{
return new(text.ToVector2(), 0);
}
}
}
@ -115,7 +127,11 @@ namespace Microsoft.Toolkit.Uwp.UI
[Pure]
public static Vector4 ToVector4(this string text)
{
if (text.Length > 0)
if (text.Length == 0)
{
return Vector4.Zero;
}
else
{
text = Unbracket(text);
@ -140,6 +156,14 @@ namespace Microsoft.Toolkit.Uwp.UI
return new(x, y, z, w);
}
}
else if (values.Length == 3)
{
return new(text.ToVector3(), 0);
}
else if (values.Length == 2)
{
return new(text.ToVector2(), 0, 0);
}
}
}
@ -159,7 +183,11 @@ namespace Microsoft.Toolkit.Uwp.UI
[Pure]
public static Quaternion ToQuaternion(this string text)
{
if (text.Length > 0)
if (text.Length == 0)
{
return new();
}
else
{
text = Unbracket(text);

Просмотреть файл

@ -2,9 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.Toolkit.Uwp.SampleApp.Models
{
public class ThicknessPropertyOptions : PropertyOptions
{
}
}
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Toolkit.Uwp.UI.Media")]

Просмотреть файл

@ -0,0 +1,310 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Linq;
using System.Numerics;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Hosting;
using Windows.UI.Xaml.Shapes;
namespace Microsoft.Toolkit.Uwp.UI
{
/// <summary>
/// A helper to add a composition based drop shadow to a <see cref="FrameworkElement"/>.
/// </summary>
public sealed class AttachedDropShadow : AttachedShadowBase
{
private const float MaxBlurRadius = 72;
/// <inheritdoc/>
public override bool IsSupported => true;
/// <inheritdoc/>
protected internal override bool SupportsOnSizeChangedEvent => true;
private static readonly TypedResourceKey<CompositionRoundedRectangleGeometry> RoundedRectangleGeometryResourceKey = "RoundedGeometry";
private static readonly TypedResourceKey<CompositionSpriteShape> ShapeResourceKey = "Shape";
private static readonly TypedResourceKey<ShapeVisual> ShapeVisualResourceKey = "ShapeVisual";
private static readonly TypedResourceKey<CompositionSurfaceBrush> SurfaceBrushResourceKey = "SurfaceBrush";
private static readonly TypedResourceKey<CompositionVisualSurface> VisualSurfaceResourceKey = "VisualSurface";
/// <summary>
/// Gets or sets a value indicating whether the panel uses an alpha mask to create a more precise shadow vs. a quicker rectangle shape.
/// </summary>
/// <remarks>
/// Turn this off to lose fidelity and gain performance of the panel.
/// </remarks>
public bool IsMasked
{
get { return (bool)GetValue(IsMaskedProperty); }
set { SetValue(IsMaskedProperty, value); }
}
/// <summary>
/// Identifies the <see cref="IsMasked"/> dependency property.
/// </summary>
public static readonly DependencyProperty IsMaskedProperty =
DependencyProperty.Register(nameof(IsMasked), typeof(bool), typeof(AttachedDropShadow), new PropertyMetadata(true, OnDependencyPropertyChanged));
/// <summary>
/// Gets or sets the roundness of the shadow's corners.
/// </summary>
public double CornerRadius
{
get => (double)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
/// <summary>
/// The <see cref="DependencyProperty"/> for <see cref="CornerRadius"/>
/// </summary>
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register(
nameof(CornerRadius),
typeof(double),
typeof(AttachedDropShadow),
new PropertyMetadata(4d, OnDependencyPropertyChanged)); // Default WinUI ControlCornerRadius is 4
/// <summary>
/// Gets or sets the <see cref="Panel"/> to be used as a backdrop to cast shadows on.
/// </summary>
public FrameworkElement CastTo
{
get { return (FrameworkElement)GetValue(CastToProperty); }
set { SetValue(CastToProperty, value); }
}
/// <summary>
/// The <see cref="DependencyProperty"/> for <see cref="CastTo"/>
/// </summary>
public static readonly DependencyProperty CastToProperty =
DependencyProperty.Register(nameof(CastTo), typeof(FrameworkElement), typeof(AttachedDropShadow), new PropertyMetadata(null, OnCastToPropertyChanged)); // TODO: Property Change
private ContainerVisual _container;
private static void OnCastToPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is AttachedDropShadow shadow)
{
if (e.OldValue is FrameworkElement element)
{
ElementCompositionPreview.SetElementChildVisual(element, null);
element.SizeChanged -= shadow.CastToElement_SizeChanged;
}
if (e.NewValue is FrameworkElement elementNew)
{
var prevContainer = shadow._container;
var child = ElementCompositionPreview.GetElementChildVisual(elementNew);
if (child is ContainerVisual visual)
{
shadow._container = visual;
}
else
{
var compositor = ElementCompositionPreview.GetElementVisual(shadow.CastTo).Compositor;
shadow._container = compositor.CreateContainerVisual();
ElementCompositionPreview.SetElementChildVisual(elementNew, shadow._container);
}
// Need to remove all old children from previous container if it's changed
if (prevContainer != null && prevContainer != shadow._container)
{
foreach (var context in shadow.EnumerateElementContexts())
{
if (context.IsInitialized &&
prevContainer.Children.Contains(context.SpriteVisual))
{
prevContainer.Children.Remove(context.SpriteVisual);
}
}
}
// Make sure all child shadows are hooked into container
foreach (var context in shadow.EnumerateElementContexts())
{
if (context.IsInitialized)
{
shadow.SetElementChildVisual(context);
}
}
elementNew.SizeChanged += shadow.CastToElement_SizeChanged;
// Re-trigger updates to all shadow locations for new parent
shadow.CastToElement_SizeChanged(null, null);
}
}
}
private void CastToElement_SizeChanged(object sender, SizeChangedEventArgs e)
{
// Don't use sender or 'e' here as related to container element not
// element for shadow, grab values off context. (Also may be null from internal call.)
foreach (var context in EnumerateElementContexts())
{
if (context.IsInitialized)
{
// TODO: Should we use ActualWidth/Height instead of RenderSize?
OnSizeChanged(context, context.Element.RenderSize, context.Element.RenderSize);
}
}
}
/// <inheritdoc/>
protected internal override void OnElementContextUninitialized(AttachedShadowElementContext context)
{
if (_container != null && _container.Children.Contains(context.SpriteVisual))
{
_container.Children.Remove(context.SpriteVisual);
}
base.OnElementContextUninitialized(context);
}
/// <inheritdoc/>
protected override void SetElementChildVisual(AttachedShadowElementContext context)
{
if (_container != null && !_container.Children.Contains(context.SpriteVisual))
{
_container.Children.InsertAtTop(context.SpriteVisual);
}
}
/// <inheritdoc/>
protected override CompositionBrush GetShadowMask(AttachedShadowElementContext context)
{
CompositionBrush mask = null;
if (DesignTimeHelpers.IsRunningInLegacyDesignerMode)
{
return null;
}
if (context.Element != null)
{
if (IsMasked)
{
// We check for IAlphaMaskProvider first, to ensure that we use the custom
// alpha mask even if Content happens to extend any of the other classes
if (context.Element is IAlphaMaskProvider maskedControl)
{
if (maskedControl.WaitUntilLoaded && !context.Element.IsLoaded)
{
context.Element.Loaded += CustomMaskedElement_Loaded;
}
else
{
mask = maskedControl.GetAlphaMask();
}
}
else if (context.Element is Image)
{
mask = ((Image)context.Element).GetAlphaMask();
}
else if (context.Element is Shape)
{
mask = ((Shape)context.Element).GetAlphaMask();
}
else if (context.Element is TextBlock)
{
mask = ((TextBlock)context.Element).GetAlphaMask();
}
}
// If we don't have a mask and have specified rounded corners, we'll generate a simple quick mask.
// This is the same code from link:AttachedCardShadow.cs:GetShadowMask
if (mask == null && SupportsCompositionVisualSurface && CornerRadius > 0)
{
// Create rounded rectangle geometry and add it to a shape
var geometry = context.GetResource(RoundedRectangleGeometryResourceKey) ?? context.AddResource(
RoundedRectangleGeometryResourceKey,
context.Compositor.CreateRoundedRectangleGeometry());
geometry.CornerRadius = new Vector2((float)CornerRadius);
var shape = context.GetResource(ShapeResourceKey) ?? context.AddResource(ShapeResourceKey, context.Compositor.CreateSpriteShape(geometry));
shape.FillBrush = context.Compositor.CreateColorBrush(Colors.Black);
// Create a ShapeVisual so that our geometry can be rendered to a visual
var shapeVisual = context.GetResource(ShapeVisualResourceKey) ??
context.AddResource(ShapeVisualResourceKey, context.Compositor.CreateShapeVisual());
shapeVisual.Shapes.Add(shape);
// Create a CompositionVisualSurface, which renders our ShapeVisual to a texture
var visualSurface = context.GetResource(VisualSurfaceResourceKey) ??
context.AddResource(VisualSurfaceResourceKey, context.Compositor.CreateVisualSurface());
visualSurface.SourceVisual = shapeVisual;
// Create a CompositionSurfaceBrush to render our CompositionVisualSurface to a brush.
// Now we have a rounded rectangle brush that can be used on as the mask for our shadow.
var surfaceBrush = context.GetResource(SurfaceBrushResourceKey) ?? context.AddResource(
SurfaceBrushResourceKey,
context.Compositor.CreateSurfaceBrush(visualSurface));
geometry.Size = visualSurface.SourceSize = shapeVisual.Size = context.Element.RenderSize.ToVector2();
mask = surfaceBrush;
}
}
// Position our shadow in the correct spot to match the corresponding element.
context.SpriteVisual.Size = context.Element.RenderSize.ToVector2();
context.SpriteVisual.Offset = context.Element.CoordinatesFrom(CastTo).ToVector3();
return mask;
}
private void CustomMaskedElement_Loaded(object sender, RoutedEventArgs e)
{
var context = GetElementContext(sender as FrameworkElement);
context.Element.Loaded -= CustomMaskedElement_Loaded;
UpdateShadowClip(context);
UpdateShadowMask(context);
}
/// <inheritdoc/>
protected internal override void OnSizeChanged(AttachedShadowElementContext context, Size newSize, Size previousSize)
{
var sizeAsVec2 = newSize.ToVector2();
context.SpriteVisual.Size = sizeAsVec2;
context.SpriteVisual.Offset = context.Element.CoordinatesFrom(CastTo).ToVector3();
UpdateShadowClip(context);
base.OnSizeChanged(context, newSize, previousSize);
}
/// <inheritdoc/>
protected override void OnPropertyChanged(AttachedShadowElementContext context, DependencyProperty property, object oldValue, object newValue)
{
if (property == IsMaskedProperty)
{
UpdateShadowMask(context);
}
else if (property == CornerRadiusProperty)
{
var geometry = context.GetResource(RoundedRectangleGeometryResourceKey);
if (geometry != null)
{
geometry.CornerRadius = new Vector2((float)(double)newValue);
}
UpdateShadowMask(context);
}
else
{
base.OnPropertyChanged(context, property, oldValue, newValue);
}
}
}
}

Просмотреть файл

@ -0,0 +1,290 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using Windows.Foundation;
using Windows.Foundation.Metadata;
using Windows.UI;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Hosting;
namespace Microsoft.Toolkit.Uwp.UI
{
/// <summary>
/// The base class for attached shadows.
/// </summary>
public abstract class AttachedShadowBase : DependencyObject, IAttachedShadow
{
/// <summary>
/// Gets a value indicating whether or not Composition's VisualSurface is supported.
/// </summary>
protected static readonly bool SupportsCompositionVisualSurface = ApiInformation.IsTypePresent(typeof(CompositionVisualSurface).FullName);
/// <summary>
/// The <see cref="DependencyProperty"/> for <see cref="BlurRadius"/>.
/// </summary>
public static readonly DependencyProperty BlurRadiusProperty =
DependencyProperty.Register(nameof(BlurRadius), typeof(double), typeof(AttachedShadowBase), new PropertyMetadata(12d, OnDependencyPropertyChanged));
/// <summary>
/// The <see cref="DependencyProperty"/> for <see cref="Color"/>.
/// </summary>
public static readonly DependencyProperty ColorProperty =
DependencyProperty.Register(nameof(Color), typeof(Color), typeof(AttachedShadowBase), new PropertyMetadata(Colors.Black, OnDependencyPropertyChanged));
/// <summary>
/// The <see cref="DependencyProperty"/> for <see cref="Opacity"/>.
/// </summary>
public static readonly DependencyProperty OffsetProperty =
DependencyProperty.Register(
nameof(Offset),
typeof(string), // Needs to be string as we can't convert in XAML natively from Vector3, see https://github.com/microsoft/microsoft-ui-xaml/issues/3896
typeof(AttachedShadowBase),
new PropertyMetadata(string.Empty, OnDependencyPropertyChanged));
/// <summary>
/// The <see cref="DependencyProperty"/> for <see cref="Opacity"/>
/// </summary>
public static readonly DependencyProperty OpacityProperty =
DependencyProperty.Register(nameof(Opacity), typeof(double), typeof(AttachedShadowBase), new PropertyMetadata(1d, OnDependencyPropertyChanged));
/// <summary>
/// Gets a value indicating whether or not this <see cref="AttachedShadowBase"/> implementation is supported on the current platform.
/// </summary>
public abstract bool IsSupported { get; }
/// <summary>
/// Gets or sets the collection of <see cref="AttachedShadowElementContext"/> for each element this <see cref="AttachedShadowBase"/> is connected to.
/// </summary>
private ConditionalWeakTable<FrameworkElement, AttachedShadowElementContext> ShadowElementContextTable { get; set; }
/// <inheritdoc/>
public double BlurRadius
{
get => (double)GetValue(BlurRadiusProperty);
set => SetValue(BlurRadiusProperty, value);
}
/// <inheritdoc/>
public double Opacity
{
get => (double)GetValue(OpacityProperty);
set => SetValue(OpacityProperty, value);
}
/// <inheritdoc/>
public string Offset
{
get => (string)GetValue(OffsetProperty);
set => SetValue(OffsetProperty, value);
}
/// <inheritdoc/>
public Color Color
{
get => (Color)GetValue(ColorProperty);
set => SetValue(ColorProperty, value);
}
/// <summary>
/// Gets a value indicating whether or not OnSizeChanged should be called when <see cref="FrameworkElement.SizeChanged"/> is fired.
/// </summary>
protected internal abstract bool SupportsOnSizeChangedEvent { get; }
/// <summary>
/// Use this method as the <see cref="PropertyChangedCallback"/> for <see cref="DependencyProperty">DependencyProperties</see> in derived classes.
/// </summary>
protected static void OnDependencyPropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
{
(sender as AttachedShadowBase)?.CallPropertyChangedForEachElement(args.Property, args.OldValue, args.NewValue);
}
internal void ConnectElement(FrameworkElement element)
{
if (!IsSupported)
{
return;
}
ShadowElementContextTable = ShadowElementContextTable ?? new ConditionalWeakTable<FrameworkElement, AttachedShadowElementContext>();
if (ShadowElementContextTable.TryGetValue(element, out var context))
{
return;
}
context = new AttachedShadowElementContext();
context.ConnectToElement(this, element);
ShadowElementContextTable.Add(element, context);
}
internal void DisconnectElement(FrameworkElement element)
{
if (ShadowElementContextTable == null)
{
return;
}
if (ShadowElementContextTable.TryGetValue(element, out var context))
{
context.DisconnectFromElement();
ShadowElementContextTable.Remove(element);
}
}
/// <summary>
/// Override to handle when the <see cref="AttachedShadowElementContext"/> for an element is being initialized.
/// </summary>
/// <param name="context">The <see cref="AttachedShadowElementContext"/> that is being initialized.</param>
protected internal virtual void OnElementContextInitialized(AttachedShadowElementContext context)
{
OnPropertyChanged(context, OpacityProperty, Opacity, Opacity);
OnPropertyChanged(context, BlurRadiusProperty, BlurRadius, BlurRadius);
OnPropertyChanged(context, ColorProperty, Color, Color);
OnPropertyChanged(context, OffsetProperty, Offset, Offset);
UpdateShadowClip(context);
UpdateShadowMask(context);
SetElementChildVisual(context);
}
/// <summary>
/// Override to handle when the <see cref="AttachedShadowElementContext"/> for an element is being uninitialized.
/// </summary>
/// <param name="context">The <see cref="AttachedShadowElementContext"/> that is being uninitialized.</param>
protected internal virtual void OnElementContextUninitialized(AttachedShadowElementContext context)
{
context.ClearAndDisposeResources();
ElementCompositionPreview.SetElementChildVisual(context.Element, null);
}
/// <inheritdoc/>
public AttachedShadowElementContext GetElementContext(FrameworkElement element)
{
if (ShadowElementContextTable != null && ShadowElementContextTable.TryGetValue(element, out var context))
{
return context;
}
return null;
}
/// <inheritdoc/>
public IEnumerable<AttachedShadowElementContext> EnumerateElementContexts()
{
foreach (var kvp in ShadowElementContextTable)
{
yield return kvp.Value;
}
}
/// <summary>
/// Sets <see cref="AttachedShadowElementContext.SpriteVisual"/> as a child visual on <see cref="AttachedShadowElementContext.Element"/>
/// </summary>
/// <param name="context">The <see cref="AttachedShadowElementContext"/> this operaiton will be performed on.</param>
protected virtual void SetElementChildVisual(AttachedShadowElementContext context)
{
ElementCompositionPreview.SetElementChildVisual(context.Element, context.SpriteVisual);
}
private void CallPropertyChangedForEachElement(DependencyProperty property, object oldValue, object newValue)
{
if (ShadowElementContextTable == null)
{
return;
}
foreach (var context in ShadowElementContextTable)
{
if (context.Value.IsInitialized)
{
OnPropertyChanged(context.Value, property, oldValue, newValue);
}
}
}
/// <summary>
/// Get a <see cref="CompositionBrush"/> in the shape of the element that is casting the shadow.
/// </summary>
/// <returns>A <see cref="CompositionBrush"/> representing the shape of an element.</returns>
protected virtual CompositionBrush GetShadowMask(AttachedShadowElementContext context)
{
return null;
}
/// <summary>
/// Get the <see cref="CompositionClip"/> for the shadow's <see cref="SpriteVisual"/>
/// </summary>
/// <returns>A <see cref="CompositionClip"/> for the extent of the shadowed area.</returns>
protected virtual CompositionClip GetShadowClip(AttachedShadowElementContext context)
{
return null;
}
/// <summary>
/// Update the mask that gives the shadow its shape.
/// </summary>
protected void UpdateShadowMask(AttachedShadowElementContext context)
{
if (!context.IsInitialized)
{
return;
}
context.Shadow.Mask = GetShadowMask(context);
}
/// <summary>
/// Update the clipping on the shadow's <see cref="SpriteVisual"/>.
/// </summary>
protected void UpdateShadowClip(AttachedShadowElementContext context)
{
if (!context.IsInitialized)
{
return;
}
context.SpriteVisual.Clip = GetShadowClip(context);
}
/// <summary>
/// This method is called when a DependencyProperty is changed.
/// </summary>
protected virtual void OnPropertyChanged(AttachedShadowElementContext context, DependencyProperty property, object oldValue, object newValue)
{
if (!context.IsInitialized)
{
return;
}
if (property == BlurRadiusProperty)
{
context.Shadow.BlurRadius = (float)(double)newValue;
}
else if (property == OpacityProperty)
{
context.Shadow.Opacity = (float)(double)newValue;
}
else if (property == ColorProperty)
{
context.Shadow.Color = (Color)newValue;
}
else if (property == OffsetProperty)
{
context.Shadow.Offset = (Vector3)(newValue as string)?.ToVector3();
}
}
/// <summary>
/// This method is called when the element size changes, and <see cref="SupportsOnSizeChangedEvent"/> = <see cref="bool">true</see>.
/// </summary>
/// <param name="context">The <see cref="AttachedShadowElementContext"/> for the <see cref="FrameworkElement"/> firing its SizeChanged event</param>
/// <param name="newSize">The new size of the <see cref="FrameworkElement"/></param>
/// <param name="previousSize">The previous size of the <see cref="FrameworkElement"/></param>
protected internal virtual void OnSizeChanged(AttachedShadowElementContext context, Size newSize, Size previousSize)
{
}
}
}

Просмотреть файл

@ -0,0 +1,318 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Numerics;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Hosting;
namespace Microsoft.Toolkit.Uwp.UI
{
/// <summary>
/// Class which maintains the context of a <see cref="DropShadow"/> for a particular <see cref="UIElement"/> linked to the definition of that shadow provided by the <see cref="AttachedShadowBase"/> implementation being used.
/// </summary>
public sealed class AttachedShadowElementContext
{
private bool _isConnected;
private Dictionary<string, object> _resources;
/// <summary>
/// Gets a value indicating whether or not this <see cref="AttachedShadowElementContext"/> has been initialized.
/// </summary>
public bool IsInitialized { get; private set; }
/// <summary>
/// Gets the <see cref="AttachedShadowBase"/> that contains this <see cref="AttachedShadowElementContext"/>.
/// </summary>
public AttachedShadowBase Parent { get; private set; }
/// <summary>
/// Gets the <see cref="FrameworkElement"/> this instance is attached to
/// </summary>
public FrameworkElement Element { get; private set; }
/// <summary>
/// Gets the <see cref="Visual"/> for the <see cref="FrameworkElement"/> this instance is attached to.
/// </summary>
public Visual ElementVisual { get; private set; }
/// <summary>
/// Gets the <see cref="Windows.UI.Composition.Compositor"/> for this instance.
/// </summary>
public Compositor Compositor { get; private set; }
/// <summary>
/// Gets the <see cref="SpriteVisual"/> that contains the <see cref="DropShadow">shadow</see> for this instance
/// </summary>
public SpriteVisual SpriteVisual { get; private set; }
/// <summary>
/// Gets the <see cref="DropShadow"/> that is rendered on this instance's <see cref="Element"/>
/// </summary>
public DropShadow Shadow { get; private set; }
/// <summary>
/// Connects a <see cref="FrameworkElement"/> to its parent <see cref="AttachedShadowBase"/> definition.
/// </summary>
/// <param name="parent">The <see cref="AttachedShadowBase"/> that is using this context.</param>
/// <param name="element">The <see cref="FrameworkElement"/> that a shadow is being attached to.</param>
internal void ConnectToElement(AttachedShadowBase parent, FrameworkElement element)
{
if (_isConnected)
{
throw new InvalidOperationException("This AttachedShadowElementContext has already been connected to an element");
}
_isConnected = true;
Parent = parent ?? throw new ArgumentNullException(nameof(parent));
Element = element ?? throw new ArgumentNullException(nameof(element));
Element.Loaded += OnElementLoaded;
Element.Unloaded += OnElementUnloaded;
Initialize();
}
internal void DisconnectFromElement()
{
if (!_isConnected)
{
return;
}
Uninitialize();
Element.Loaded -= OnElementLoaded;
Element.Unloaded -= OnElementUnloaded;
Element = null;
Parent = null;
_isConnected = false;
}
/// <summary>
/// Force early creation of this instance's resources, otherwise they will be created automatically when <see cref="Element"/> is loaded.
/// </summary>
public void CreateResources() => Initialize(true);
private void Initialize(bool forceIfNotLoaded = false)
{
if (IsInitialized || !_isConnected || (!Element.IsLoaded && !forceIfNotLoaded))
{
return;
}
IsInitialized = true;
ElementVisual = ElementCompositionPreview.GetElementVisual(Element);
Compositor = ElementVisual.Compositor;
Shadow = Compositor.CreateDropShadow();
SpriteVisual = Compositor.CreateSpriteVisual();
SpriteVisual.RelativeSizeAdjustment = Vector2.One;
SpriteVisual.Shadow = Shadow;
if (Parent.SupportsOnSizeChangedEvent)
{
Element.SizeChanged += OnElementSizeChanged;
}
Parent?.OnElementContextInitialized(this);
}
private void Uninitialize()
{
if (!IsInitialized)
{
return;
}
IsInitialized = false;
Parent.OnElementContextUninitialized(this);
SpriteVisual.Shadow = null;
SpriteVisual.Dispose();
Shadow.Dispose();
ElementCompositionPreview.SetElementChildVisual(Element, null);
Element.SizeChanged -= OnElementSizeChanged;
SpriteVisual = null;
Shadow = null;
ElementVisual = null;
}
private void OnElementUnloaded(object sender, RoutedEventArgs e)
{
Uninitialize();
}
private void OnElementLoaded(object sender, RoutedEventArgs e)
{
Initialize();
}
private void OnElementSizeChanged(object sender, SizeChangedEventArgs e)
{
Parent?.OnSizeChanged(this, e.NewSize, e.PreviousSize);
}
/// <summary>
/// Adds a resource to this instance's resource dictionary with the specified key
/// </summary>
/// <typeparam name="T">The type of the resource being added.</typeparam>
/// <param name="key">Key to use to lookup the resource later.</param>
/// <param name="resource">Object to store within the resource dictionary.</param>
/// <returns>The added resource</returns>
public T AddResource<T>(string key, T resource)
{
_resources = _resources ?? new Dictionary<string, object>();
if (_resources.ContainsKey(key))
{
_resources[key] = resource;
}
else
{
_resources.Add(key, resource);
}
return resource;
}
/// <summary>
/// Retrieves a resource with the specified key and type if it exists
/// </summary>
/// <typeparam name="T">The type of the resource being retrieved.</typeparam>
/// <param name="key">Key to use to lookup the resource.</param>
/// <param name="resource">Object to retrieved from the resource dictionary or default value.</param>
/// <returns>True if the resource exists, false otherwise</returns>
public bool TryGetResource<T>(string key, out T resource)
{
if (_resources != null && _resources.TryGetValue(key, out var objResource) && objResource is T tResource)
{
resource = tResource;
return true;
}
resource = default;
return false;
}
/// <summary>
/// Retries a resource with the specified key and type
/// </summary>
/// <typeparam name="T">The type of the resource being retrieved.</typeparam>
/// <param name="key">Key to use to lookup the resource.</param>
/// <returns>The resource if available, otherwise default value.</returns>
public T GetResource<T>(string key)
{
if (TryGetResource(key, out T resource))
{
return resource;
}
return default;
}
/// <summary>
/// Removes an existing resource with the specified key and type
/// </summary>
/// <typeparam name="T">The type of the resource being removed.</typeparam>
/// <param name="key">Key to use to lookup the resource.</param>
/// <returns>The resource that was removed, if any</returns>
public T RemoveResource<T>(string key)
{
if (_resources.TryGetValue(key, out var objResource))
{
_resources.Remove(key);
if (objResource is T resource)
{
return resource;
}
}
return default;
}
/// <summary>
/// Removes an existing resource with the specified key and type, and <see cref="IDisposable.Dispose">disposes</see> it
/// </summary>
/// <typeparam name="T">The type of the resource being removed.</typeparam>
/// <param name="key">Key to use to lookup the resource.</param>
/// <returns>The resource that was removed, if any</returns>
public T RemoveAndDisposeResource<T>(string key)
where T : IDisposable
{
if (_resources.TryGetValue(key, out var objResource))
{
_resources.Remove(key);
if (objResource is T resource)
{
resource.Dispose();
return resource;
}
}
return default;
}
/// <summary>
/// Adds a resource to this instance's collection with the specified key
/// </summary>
/// <typeparam name="T">The type of the resource being added.</typeparam>
/// <returns>The resource that was added</returns>
internal T AddResource<T>(TypedResourceKey<T> key, T resource) => AddResource(key.Key, resource);
/// <summary>
/// Retrieves a resource with the specified key and type if it exists
/// </summary>
/// <typeparam name="T">The type of the resource being retrieved.</typeparam>
/// <returns>True if the resource exists, false otherwise</returns>
internal bool TryGetResource<T>(TypedResourceKey<T> key, out T resource) => TryGetResource(key.Key, out resource);
/// <summary>
/// Retries a resource with the specified key and type
/// </summary>
/// <typeparam name="T">The type of the resource being retrieved.</typeparam>
/// <returns>The resource if it exists or a default value.</returns>
internal T GetResource<T>(TypedResourceKey<T> key) => GetResource<T>(key.Key);
/// <summary>
/// Removes an existing resource with the specified key and type
/// </summary>
/// <typeparam name="T">The type of the resource being removed.</typeparam>
/// <returns>The resource that was removed, if any</returns>
internal T RemoveResource<T>(TypedResourceKey<T> key) => RemoveResource<T>(key.Key);
/// <summary>
/// Removes an existing resource with the specified key and type, and <see cref="IDisposable.Dispose">disposes</see> it
/// </summary>
/// <typeparam name="T">The type of the resource being removed.</typeparam>
/// <returns>The resource that was removed, if any</returns>
internal T RemoveAndDisposeResource<T>(TypedResourceKey<T> key)
where T : IDisposable => RemoveAndDisposeResource<T>(key.Key);
/// <summary>
/// Disposes of any resources that implement <see cref="IDisposable"/> and then clears all resources
/// </summary>
public void ClearAndDisposeResources()
{
if (_resources != null)
{
foreach (var kvp in _resources)
{
(kvp.Value as IDisposable)?.Dispose();
}
_resources.Clear();
}
}
}
}

Просмотреть файл

@ -0,0 +1,58 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Windows.UI.Xaml;
namespace Microsoft.Toolkit.Uwp.UI
{
/// <summary>
/// Helper class for attaching <see cref="AttachedShadowBase"/> shadows to <see cref="FrameworkElement"/>s.
/// </summary>
public static class Effects
{
/// <summary>
/// Gets the shadow attached to a <see cref="FrameworkElement"/> by getting the value of the <see cref="ShadowProperty"/> property.
/// </summary>
/// <param name="obj">The <see cref="FrameworkElement"/> the <see cref="AttachedShadowBase"/> is attached to.</param>
/// <returns>The <see cref="AttachedShadowBase"/> that is attached to the <paramref name="obj">FrameworkElement.</paramref></returns>
public static AttachedShadowBase GetShadow(FrameworkElement obj)
{
return (AttachedShadowBase)obj.GetValue(ShadowProperty);
}
/// <summary>
/// Attaches a shadow to an element by setting the <see cref="ShadowProperty"/> property.
/// </summary>
/// <param name="obj">The <see cref="FrameworkElement"/> to attach the shadow to.</param>
/// <param name="value">The <see cref="AttachedShadowBase"/> that will be attached to the element</param>
public static void SetShadow(FrameworkElement obj, AttachedShadowBase value)
{
obj.SetValue(ShadowProperty, value);
}
/// <summary>
/// Attached <see cref="DependencyProperty"/> for setting an <see cref="AttachedShadowBase"/> to a <see cref="FrameworkElement"/>.
/// </summary>
public static readonly DependencyProperty ShadowProperty =
DependencyProperty.RegisterAttached("Shadow", typeof(AttachedShadowBase), typeof(Effects), new PropertyMetadata(null, OnShadowChanged));
private static void OnShadowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is FrameworkElement element))
{
return;
}
if (e.OldValue is AttachedShadowBase oldShadow)
{
oldShadow.DisconnectElement(element);
}
if (e.NewValue is AttachedShadowBase newShadow)
{
newShadow.ConnectElement(element);
}
}
}
}

Просмотреть файл

@ -11,6 +11,11 @@ namespace Microsoft.Toolkit.Uwp.UI
/// </summary>
public interface IAlphaMaskProvider
{
/// <summary>
/// Gets a value indicating whether the AlphaMask needs to be retrieved after the element has loaded.
/// </summary>
bool WaitUntilLoaded { get; }
/// <summary>
/// This method should return the appropiate alpha mask to be used in the shadow of this control
/// </summary>

Просмотреть файл

@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Numerics;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml;
namespace Microsoft.Toolkit.Uwp.UI
{
/// <summary>
/// Interface representing the common properties found within an attached shadow, <see cref="AttachedShadowBase"/> for implementation.
/// </summary>
public interface IAttachedShadow
{
/// <summary>
/// Gets or sets the blur radius of the shadow.
/// </summary>
double BlurRadius { get; set; }
/// <summary>
/// Gets or sets the opacity of the shadow.
/// </summary>
double Opacity { get; set; }
/// <summary>
/// Gets or sets the offset of the shadow as a string representation of a <see cref="Vector3"/>.
/// </summary>
string Offset { get; set; }
/// <summary>
/// Gets or sets the color of the shadow.
/// </summary>
Color Color { get; set; }
/// <summary>
/// Get the associated <see cref="AttachedShadowElementContext"/> for the specified <see cref="FrameworkElement"/>.
/// </summary>
/// <returns>The <see cref="AttachedShadowElementContext"/> for the element.</returns>
AttachedShadowElementContext GetElementContext(FrameworkElement element);
/// <summary>
/// Gets an enumeration over the current list of <see cref="AttachedShadowElementContext"/> of elements using this shared shadow definition.
/// </summary>
/// <returns>Enumeration of <see cref="AttachedShadowElementContext"/> objects.</returns>
IEnumerable<AttachedShadowElementContext> EnumerateElementContexts();
}
}

Просмотреть файл

@ -0,0 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace Microsoft.Toolkit.Uwp.UI
{
/// <summary>
/// A generic class that can be used to retrieve keyed resources of the specified type.
/// </summary>
/// <typeparam name="TValue">The <see cref="Type"/> of resource the <see cref="TypedResourceKey{TValue}"/> will retrieve.</typeparam>
internal sealed class TypedResourceKey<TValue>
{
/// <summary>
/// Initializes a new instance of the <see cref="TypedResourceKey{TValue}"/> class with the specified key.
/// </summary>
/// <param name="key">The resource's key</param>
public TypedResourceKey(string key) => Key = key;
/// <summary>
/// Gets the key of the resource to be retrieved.
/// </summary>
public string Key { get; }
/// <summary>
/// Implicit operator for transforming a string into a <see cref="TypedResourceKey{TValue}"/> key.
/// </summary>
/// <param name="key">The key string.</param>
public static implicit operator TypedResourceKey<TValue>(string key)
{
return new TypedResourceKey<TValue>(key);
}
}
}

Просмотреть файл

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Contracts;
using System.Numerics;
using System.Runtime.CompilerServices;
using Point = Windows.Foundation.Point;
using Rect = Windows.Foundation.Rect;
@ -54,5 +55,17 @@ namespace Microsoft.Toolkit.Uwp
{
return new Rect(point, size);
}
/// <summary>
/// Creates a new <see cref="Vector3"/> of the specified point with 0 for the <see cref="Vector3.Z"/> coordinate.
/// </summary>
/// <param name="point"><see cref="Point"/> to transform to a <see cref="Vector3"/>.</param>
/// <returns>New <see cref="Vector3"/> representing the X,Y position of the <see cref="Point"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3 ToVector3(this Point point)
{
return new Vector3(point.ToVector2(), 0f);
}
}
}

Просмотреть файл

@ -59,7 +59,15 @@ namespace UnitTests.Extensions
[TestCategory("StringExtensions")]
[TestMethod]
[DataRow("")]
public void Test_StringExtensions_ToVector2_Zero()
{
var value = string.Empty.ToVector2();
Assert.AreEqual(Vector2.Zero, value);
}
[TestCategory("StringExtensions")]
[TestMethod]
[DataRow("Hello")]
[DataRow("1, 2, 3")]
[DataRow("<1, 2, 3")]
@ -117,11 +125,28 @@ namespace UnitTests.Extensions
[TestCategory("StringExtensions")]
[TestMethod]
[DataRow("")]
public void Test_StringExtensions_ToVector3_Zero()
{
var value = string.Empty.ToVector3();
Assert.AreEqual(Vector3.Zero, value);
}
[TestCategory("StringExtensions")]
[TestMethod]
public void Test_StringExtensions_ToVector3_FromTwoValues()
{
var value = "4, 3".ToVector3();
Assert.AreEqual(new Vector3(new Vector2(4, 3), 0), value);
}
[TestCategory("StringExtensions")]
[TestMethod]
[DataRow("Hello")]
[DataRow("1, 2")]
[DataRow("1, ")]
[DataRow("1, 2, 3, 99")]
[DataRow("<1, 2>")]
[DataRow("1, 2>")]
[DataRow("<1, 2, 3")]
[DataRow("<1, 2, 3, 4>")]
[ExpectedException(typeof(FormatException))]
@ -179,12 +204,38 @@ namespace UnitTests.Extensions
[TestCategory("StringExtensions")]
[TestMethod]
[DataRow("")]
public void Test_StringExtensions_ToVector4_Zero()
{
var value = string.Empty.ToVector4();
Assert.AreEqual(Vector4.Zero, value);
}
[TestCategory("StringExtensions")]
[TestMethod]
public void Test_StringExtensions_ToVector4_FromTwoValues()
{
var value = "4, 3".ToVector4();
Assert.AreEqual(new Vector4(new Vector2(4, 3), 0, 0), value);
}
[TestCategory("StringExtensions")]
[TestMethod]
public void Test_StringExtensions_ToVector4_FromThreeValues()
{
var value = "4, 3, -2".ToVector4();
Assert.AreEqual(new Vector4(new Vector3(4, 3, -2), 0), value);
}
[TestCategory("StringExtensions")]
[TestMethod]
[DataRow("Hello")]
[DataRow("1, 2")]
[DataRow("1, 2, 3")]
[DataRow("1, 2, ")]
[DataRow("1, 2, 3, ")]
[DataRow("1, 2, 3, 99, 100")]
[DataRow("<1, 2, 3>")]
[DataRow("<1, 2, 3")]
[DataRow("<1, 2, 3, 4")]
[DataRow("<1, 2, 3, 4, 5>")]
[ExpectedException(typeof(FormatException))]
@ -223,7 +274,15 @@ namespace UnitTests.Extensions
[TestCategory("StringExtensions")]
[TestMethod]
[DataRow("")]
public void Test_StringExtensions_ToQuaternion_Zero()
{
var value = string.Empty.ToQuaternion();
Assert.AreEqual(default(Quaternion), value);
}
[TestCategory("StringExtensions")]
[TestMethod]
[DataRow("Hello")]
[DataRow("1, 2")]
[DataRow("1, 2, 3")]