LottieViewer: UI for color palette editing, and show marker values in info panel. (#332)

* LottieViewer: allow palette colors to be changed, and markers in the info panel.

This change required more space for the color picker, and that it didn't cover up the playing Lottie, so the info and play speed and color picker are all now implemented as panels that slide out from the right.
This commit is contained in:
Simeon 2020-08-31 17:20:26 -07:00 коммит произвёл GitHub
Родитель 62f57788b4
Коммит 6cd496c890
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
41 изменённых файлов: 2177 добавлений и 937 удалений

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

@ -139,6 +139,10 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "LottieMetadata", "source\Lo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LottieMetadata.dll", "dlls\LottieMetadata\LottieMetadata.dll.csproj", "{25CEB8B8-90E0-4D23-9978-0CD83889D4AC}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "CompMetadata", "source\CompMetadata\CompMetadata.shproj", "{B0197C19-BDF5-473E-A022-E21F6122EEE5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompMetadata.dll", "dlls\CompMetadata\CompMetadata.dll.csproj", "{A262757C-9F1A-4F6E-9188-849F4B709D67}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
source\LottieToWinComp\LottieToWinComp.projitems*{0340244a-683c-405e-838b-f93872779532}*SharedItemsImports = 13
@ -150,6 +154,7 @@ Global
source\WinUIXamlMediaData\WinUIXamlMediaData.projitems*{30059ca7-0745-4eec-8d11-b14850a70c98}*SharedItemsImports = 13
source\YamlData\YamlData.projitems*{39c6b7f3-5e75-4019-82ab-00fd8a0a06e2}*SharedItemsImports = 13
source\LottieReader\LottieReader.projitems*{4e7d8957-3f5f-46e1-99a8-2012b806c9b0}*SharedItemsImports = 13
source\CompMetadata\CompMetadata.projitems*{5120efd7-a556-46bf-8d56-f65f1ef9a305}*SharedItemsImports = 4
source\GenericData\GenericData.projitems*{5120efd7-a556-46bf-8d56-f65f1ef9a305}*SharedItemsImports = 4
source\LottieData\LottieData.projitems*{5120efd7-a556-46bf-8d56-f65f1ef9a305}*SharedItemsImports = 4
source\LottieMetadata\LottieMetadata.projitems*{5120efd7-a556-46bf-8d56-f65f1ef9a305}*SharedItemsImports = 4
@ -169,11 +174,14 @@ Global
source\Lottie\Lottie.projitems*{8ef7bd77-28e9-4998-8dbb-8036f988fe65}*SharedItemsImports = 13
source\UIData\UIData.projitems*{9a99e690-71d2-4e26-9000-0e0920394dfe}*SharedItemsImports = 5
source\UIDataCodeGen\UIDataCodeGen.projitems*{9b6c0b7f-0d0f-4086-9746-0d34d7667db5}*SharedItemsImports = 5
source\CompMetadata\CompMetadata.projitems*{a262757c-9f1a-4f6e-9188-849f4b709d67}*SharedItemsImports = 5
source\GenericData\GenericData.projitems*{a687177e-31ff-4f05-89c6-03657c96a166}*SharedItemsImports = 5
source\CompMetadata\CompMetadata.projitems*{b0197c19-bdf5-473e-a022-e21f6122eee5}*SharedItemsImports = 13
source\LottieData\LottieData.projitems*{b3db16ee-a821-4474-a188-e64926529bbd}*SharedItemsImports = 13
source\LottieReader\LottieReader.projitems*{bb081e5a-cf3c-490f-8f8e-450a79f6ca33}*SharedItemsImports = 5
source\LottieMetadata\LottieMetadata.projitems*{bcedf904-f986-42ec-a22d-e0662777b7f9}*SharedItemsImports = 5
source\LottieToWinComp\LottieToWinComp.projitems*{bcedf904-f986-42ec-a22d-e0662777b7f9}*SharedItemsImports = 5
source\CompMetadata\CompMetadata.projitems*{cb12d5ba-a6fe-41e2-b555-83c903cce92a}*SharedItemsImports = 5
source\GenericData\GenericData.projitems*{cb12d5ba-a6fe-41e2-b555-83c903cce92a}*SharedItemsImports = 5
source\LottieData\LottieData.projitems*{cb12d5ba-a6fe-41e2-b555-83c903cce92a}*SharedItemsImports = 5
source\LottieMetadata\LottieMetadata.projitems*{cb12d5ba-a6fe-41e2-b555-83c903cce92a}*SharedItemsImports = 5
@ -188,6 +196,7 @@ Global
source\YamlData\YamlData.projitems*{cb587630-3cfd-4bb3-867c-3f5b1ffbc738}*SharedItemsImports = 5
source\WinCompData\WinCompData.projitems*{d02870de-7ded-4916-85d4-3175ceedef74}*SharedItemsImports = 13
source\UIDataCodeGen\UIDataCodeGen.projitems*{d02be6c8-14db-4b4f-8600-f3c9b69c104d}*SharedItemsImports = 13
source\CompMetadata\CompMetadata.projitems*{e392bad0-f936-4b64-a445-552597795cc7}*SharedItemsImports = 5
source\GenericData\GenericData.projitems*{e392bad0-f936-4b64-a445-552597795cc7}*SharedItemsImports = 5
source\LottieData\LottieData.projitems*{e392bad0-f936-4b64-a445-552597795cc7}*SharedItemsImports = 5
source\LottieMetadata\LottieMetadata.projitems*{e392bad0-f936-4b64-a445-552597795cc7}*SharedItemsImports = 5
@ -421,6 +430,18 @@ Global
{25CEB8B8-90E0-4D23-9978-0CD83889D4AC}.Release|ARM64.ActiveCfg = Release|Any CPU
{25CEB8B8-90E0-4D23-9978-0CD83889D4AC}.Release|x64.ActiveCfg = Release|Any CPU
{25CEB8B8-90E0-4D23-9978-0CD83889D4AC}.Release|x86.ActiveCfg = Release|Any CPU
{A262757C-9F1A-4F6E-9188-849F4B709D67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A262757C-9F1A-4F6E-9188-849F4B709D67}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A262757C-9F1A-4F6E-9188-849F4B709D67}.Debug|ARM.ActiveCfg = Debug|Any CPU
{A262757C-9F1A-4F6E-9188-849F4B709D67}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{A262757C-9F1A-4F6E-9188-849F4B709D67}.Debug|x64.ActiveCfg = Debug|Any CPU
{A262757C-9F1A-4F6E-9188-849F4B709D67}.Debug|x86.ActiveCfg = Debug|Any CPU
{A262757C-9F1A-4F6E-9188-849F4B709D67}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A262757C-9F1A-4F6E-9188-849F4B709D67}.Release|Any CPU.Build.0 = Release|Any CPU
{A262757C-9F1A-4F6E-9188-849F4B709D67}.Release|ARM.ActiveCfg = Release|Any CPU
{A262757C-9F1A-4F6E-9188-849F4B709D67}.Release|ARM64.ActiveCfg = Release|Any CPU
{A262757C-9F1A-4F6E-9188-849F4B709D67}.Release|x64.ActiveCfg = Release|Any CPU
{A262757C-9F1A-4F6E-9188-849F4B709D67}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -452,6 +473,8 @@ Global
{9B6C0B7F-0D0F-4086-9746-0D34D7667DB5} = {C75BD686-21A6-4EB3-8D4B-D5A01C019C52}
{04B43A1A-DDFB-4A61-BF36-39F5E666C702} = {AB232F35-AAF7-4AE2-B1D2-45DD9BC2F7D7}
{25CEB8B8-90E0-4D23-9978-0CD83889D4AC} = {C75BD686-21A6-4EB3-8D4B-D5A01C019C52}
{B0197C19-BDF5-473E-A022-E21F6122EEE5} = {AB232F35-AAF7-4AE2-B1D2-45DD9BC2F7D7}
{A262757C-9F1A-4F6E-9188-849F4B709D67} = {C75BD686-21A6-4EB3-8D4B-D5A01C019C52}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {51B9BB4C-5196-41CF-950C-12B04AD8A61C}

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

@ -47,6 +47,7 @@
</PackageReference>
</ItemGroup>
<Import Project="..\source\CompMetadata\CompMetadata.projitems" Label="Shared" />
<Import Project="..\source\GenericData\GenericData.projitems" Label="Shared" />
<Import Project="..\source\Lottie\Lottie.projitems" Label="Shared" />
<Import Project="..\source\LottieData\LottieData.projitems" Label="Shared" />

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

@ -40,5 +40,6 @@
<Import Project="..\source\YamlData\YamlData.projitems" Label="Shared" />
<Import Project="..\source\GenericData\GenericData.projitems" Label="Shared" />
<Import Project="..\source\UIDataCodeGen\UIDataCodeGen.projitems" Label="Shared" />
<Import Project="..\source\CompMetadata\CompMetadata.projitems" Label="Shared" />
</Project>

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

@ -92,6 +92,8 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "UIDataCodeGen", "..\source\
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "LottieMetadata", "..\source\LottieMetadata\LottieMetadata.shproj", "{04B43A1A-DDFB-4A61-BF36-39F5E666C702}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "CompMetadata", "..\source\CompMetadata\CompMetadata.shproj", "{B0197C19-BDF5-473E-A022-E21F6122EEE5}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\source\LottieToWinComp\LottieToWinComp.projitems*{0340244a-683c-405e-838b-f93872779532}*SharedItemsImports = 13
@ -102,7 +104,9 @@ Global
..\source\LottieReader\LottieReader.projitems*{4e7d8957-3f5f-46e1-99a8-2012b806c9b0}*SharedItemsImports = 13
..\source\UIData\UIData.projitems*{74601e6c-2dfe-4842-b170-047941abff2c}*SharedItemsImports = 13
..\source\GenericData\GenericData.projitems*{77bcd724-8555-463b-985f-f8e8110164c4}*SharedItemsImports = 13
..\source\CompMetadata\CompMetadata.projitems*{b0197c19-bdf5-473e-a022-e21f6122eee5}*SharedItemsImports = 13
..\source\LottieData\LottieData.projitems*{b3db16ee-a821-4474-a188-e64926529bbd}*SharedItemsImports = 13
..\source\CompMetadata\CompMetadata.projitems*{cb12d5ba-a6fe-41e2-b555-83c903cce92a}*SharedItemsImports = 5
..\source\GenericData\GenericData.projitems*{cb12d5ba-a6fe-41e2-b555-83c903cce92a}*SharedItemsImports = 5
..\source\LottieData\LottieData.projitems*{cb12d5ba-a6fe-41e2-b555-83c903cce92a}*SharedItemsImports = 5
..\source\LottieMetadata\LottieMetadata.projitems*{cb12d5ba-a6fe-41e2-b555-83c903cce92a}*SharedItemsImports = 5
@ -160,6 +164,7 @@ Global
{BDB88D65-320E-4293-9E55-1D3ED8332FF6} = {AB232F35-AAF7-4AE2-B1D2-45DD9BC2F7D7}
{D02BE6C8-14DB-4B4F-8600-F3C9B69C104D} = {AB232F35-AAF7-4AE2-B1D2-45DD9BC2F7D7}
{04B43A1A-DDFB-4A61-BF36-39F5E666C702} = {AB232F35-AAF7-4AE2-B1D2-45DD9BC2F7D7}
{B0197C19-BDF5-473E-A022-E21F6122EEE5} = {AB232F35-AAF7-4AE2-B1D2-45DD9BC2F7D7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {51B9BB4C-5196-41CF-950C-12B04AD8A61C}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -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 System.ComponentModel;
using Windows.UI;
namespace LottieViewer
{
// An observable named color with the ability to change the color value.
public sealed class ColorPaletteEntry : INotifyPropertyChanged
{
Color _initialColor;
Color _color;
internal ColorPaletteEntry(Color color, string name)
{
_initialColor = color;
_color = color;
Name = name;
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// If true, changing the Color will also change the InitialColor to keep it the
/// same as Color. This is used to special case the "Background" color, which does
/// not have an initial color.
/// </summary>
public bool IsInitialColorSameAsColor { get; set; }
public Color Color
{
get => _color;
set
{
// Check whether the color is actually changing, and ignore it if it isn't.
// This check ensures we can use two-way binding without infinite recursion.
if (value != _color)
{
_color = value;
if (IsInitialColorSameAsColor)
{
_initialColor = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.InitialColor)));
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Color)));
}
}
}
public Color InitialColor => _initialColor;
// A name that describes the palette entry.
public string Name { get; set; }
}
}

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

@ -138,19 +138,26 @@
</Compile>
<Compile Include="AnimatedVisuals\UiFeedbackAnimations.cs" />
<Compile Include="AnimatedVisuals\LottieLogo.cs" />
<Compile Include="ColorPaletteEntry.cs" />
<Compile Include="FeedbackLottie.xaml.cs">
<DependentUpon>FeedbackLottie.xaml</DependentUpon>
</Compile>
<Compile Include="LottieVisualDiagnosticsViewModel.cs" />
<Compile Include="ViewModel\LottieVisualDiagnosticsViewModel.cs" />
<Compile Include="MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="PaletteColorPicker.xaml.cs">
<DependentUpon>PaletteColorPicker.xaml</DependentUpon>
</Compile>
<Compile Include="Scrubber.xaml.cs">
<DependentUpon>Scrubber.xaml</DependentUpon>
</Compile>
<Compile Include="Stage.xaml.cs">
<DependentUpon>Stage.xaml</DependentUpon>
</Compile>
<Compile Include="ViewModel\Marker.cs" />
<Compile Include="ViewModel\MarkerWithDuration.cs" />
<Compile Include="ViewModel\PairOfStrings.cs" />
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
@ -211,6 +218,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="PaletteColorPicker.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Scrubber.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -251,6 +262,7 @@
<Import Project="..\source\WinUIXamlMediaData\WinUIXamlMediaData.projitems" Label="Shared" />
<Import Project="..\source\YamlData\YamlData.projitems" Label="Shared" />
<Import Project="..\source\GenericData\GenericData.projitems" Label="Shared" />
<Import Project="..\source\CompMetadata\CompMetadata.projitems" Label="Shared" />
<Target Name="Pack">
<!-- Dummy target to mute warnings about attempts to create a NuPkg -->
</Target>

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

@ -3,15 +3,15 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:LottieViewer"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
RequestedTheme="Default"
xmlns:lottie="using:Microsoft.Toolkit.Uwp.UI.Lottie"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewmodel="using:LottieViewer.ViewModel"
Visibility="Visible"
mc:Ignorable="d">
<Page.Resources>
<local:VisiblityConverter x:Key="VisibilityConverter" />
<local:FloatFormatter x:Key="floatFormatter" />
<SolidColorBrush x:Key="ArtboardBrush"
Color="White" />
<SolidColorBrush x:Name="ArtboardBrush"
Color="{x:Bind BackgroundColor.Color, Mode=OneWay}" />
</Page.Resources>
<RelativePanel AllowDrop="True"
@ -28,112 +28,205 @@
<StackPanel HorizontalAlignment="Left"
Orientation="Horizontal">
<!-- Open file (alternative: 0xe8e5) -->
<Button Click="PickFile_Click"
<ToggleButton Click="PickFile_Click"
x:Name="PickFile"
AutomationProperties.Name="Pick a Lottie file"
Style="{StaticResource ControlsButtonStyle}"
Style="{StaticResource ControlsToggleButtonStyle}"
ToolTipService.ToolTip="Pick a Lottie file">
&#xf12b;
</Button>
</ToggleButton>
</StackPanel>
<StackPanel HorizontalAlignment="Right"
Orientation="Horizontal">
<!-- Paint palette -->
<Button IsEnabled="{x:Bind _stage.Player.IsAnimatedVisualLoaded, Mode=OneWay}"
AutomationProperties.Name="Pick background color"
Style="{StaticResource ControlsButtonStyle}"
ToolTipService.ToolTip="Background color">
<Button.Flyout>
<Flyout FlyoutPresenterStyle="{StaticResource AcrylicFlyoutPresenter}">
<ColorPicker Background="Transparent"
ColorSpectrumComponents="HueSaturation"
ColorSpectrumShape="Ring"
IsColorChannelTextInputVisible="False"
IsColorPreviewVisible="True"
IsColorSliderVisible="True"
IsColorSpectrumVisible="True"
IsHexInputVisible="True"
Color="{Binding Source={StaticResource ArtboardBrush}, Path=Color, Mode=TwoWay}" />
</Flyout>
</Button.Flyout>
<ToggleButton
Checked="ControlPanelButtonChecked"
Unchecked="ControlPanelButtonUnchecked"
x:Name="PaletteButton"
AutomationProperties.Name="Pick colors"
ToolTipService.ToolTip="Color palette"
Style="{StaticResource ControlsToggleButtonStyle}">
&#xe790;
</Button>
<!-- Play speed -->
<Button IsEnabled="{x:Bind _stage.Player.IsAnimatedVisualLoaded, Mode=OneWay}"
Style="{StaticResource ControlsButtonStyle}"
AutomationProperties.Name="Pick play speed"
ToolTipService.ToolTip="Play speed">
<Button.Flyout>
<Flyout FlyoutPresenterStyle="{StaticResource AcrylicFlyoutPresenter}">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock>Play speed =</TextBlock>
<TextBlock Text="{x:Bind _stage.Player.PlaybackRate, Mode=OneWay, Converter={StaticResource floatFormatter}}" />
</StackPanel>
<Slider Width="300"
HorizontalAlignment="Stretch"
LargeChange="1"
Maximum="2.5"
Minimum="-2.5"
SmallChange="0.1"
StepFrequency="0.1"
TickFrequency="0.5"
TickPlacement="BottomRight"
Value="{x:Bind _stage.Player.PlaybackRate, Mode=TwoWay}" />
</StackPanel>
</Flyout>
</Button.Flyout>
&#xEC4a;
</Button>
<!-- Issues -->
<Button Foreground="Orange"
IsEnabled="{x:Bind _stage.Diagnostics.PlayerHasIssues, Mode=OneWay}"
Style="{StaticResource ControlsButtonStyle}"
AutomationProperties.Name="View issues"
ToolTipService.ToolTip="View issues">
<Button.Flyout>
<Flyout>
<StackPanel>
<TextBlock HorizontalAlignment="Center"
FontWeight="Bold">
This Lottie has some issues ...
</TextBlock>
<ItemsControl Margin="12"
ItemsSource="{x:Bind _stage.Diagnostics.PlayerIssues, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock MinWidth="60">
<Hyperlink NavigateUri="{Binding Path=Url}"><Run FontWeight="Bold"
Text="{Binding Path=Code}" /></Hyperlink>
</TextBlock>
<TextBlock Text="{Binding Path=Description}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button HorizontalAlignment="Center"
Click="CopyIssuesToClipboard">
Copy to clipboard
</Button>
</StackPanel>
</Flyout>
</Button.Flyout>
&#xE7BA;
</Button>
</ToggleButton>
<!-- Info -->
<ToggleButton
x:Name="InfoButton"
Checked="ControlPanelButtonChecked"
Unchecked="ControlPanelButtonUnchecked"
AutomationProperties.Name="View Lottie file info"
ToolTipService.ToolTip="Lottie file info"
Style="{StaticResource ControlsToggleButtonStyle}">
<Grid>
<!-- Switch icon based on whether or not the Lottie file has any issues. -->
<TextBlock Text="&#xE946;" Visibility="{x:Bind _stage.DiagnosticsViewModel.HasIssues, Converter={StaticResource VisibilityConverter}, ConverterParameter=not, Mode=OneWay}"/>
<!-- Different color if there are issues. -->
<TextBlock Text="&#xE946;" Visibility="{x:Bind _stage.DiagnosticsViewModel.HasIssues, Converter={StaticResource VisibilityConverter}, Mode=OneWay}" Foreground="Orange"/>
</Grid>
</ToggleButton>
</StackPanel>
</Grid>
<!-- The stage. This is where the Lotties are displayed. -->
<Border Background="{StaticResource StageBackgroundBrush}"
<Grid Background="{StaticResource StageBackgroundBrush}"
RelativePanel.Above="Controls"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.Below="TopControls"
RelativePanel.RightOf="PlayerControls">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<x:Double x:Key="ControlPanelWidth">355</x:Double>
</Grid.Resources>
<local:Stage x:Name="_stage"
ArtboardColor="{Binding Source={StaticResource ArtboardBrush}, Path=Color}" />
</Border>
<!-- The stage. This is where the Lotties are displayed. -->
<local:Stage x:Name="_stage" ArtboardColor="{x:Bind ArtboardBrush.Color, Mode=OneWay}" />
<!-- Control panel. This shows to the right of the stage. Only one panel is allowed to be
open at a time. This is ensured through code behind. -->
<Grid x:Name="ControlPanel" Grid.Column="1" Background="{StaticResource DropTargetBrush}">
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White" />
</Style>
</Grid.Resources>
<!-- Color palette panel -->
<ScrollViewer x:Name="ColorPanel"
Visibility="{x:Bind PaletteButton.IsChecked, Converter={StaticResource VisibilityConverter}, Mode=OneWay}"
Width="{StaticResource ControlPanelWidth}">
<local:PaletteColorPicker
Height="{x:Bind ColorPanel.ViewportHeight, Mode=OneWay}"
MinHeight="500"
x:Name="_paletteColorPicker"
Grid.Column="1"
DiagnosticsViewModel="{x:Bind _stage.DiagnosticsViewModel}"/>
</ScrollViewer>
<!-- Info panel -->
<ScrollViewer
x:Name="InfoPanel"
Width="{StaticResource ControlPanelWidth}"
Visibility="{x:Bind InfoButton.IsChecked, Converter={StaticResource VisibilityConverter}, Mode=OneWay}">
<StackPanel Padding="20,10,20,0">
<!-- Issues. Collapsed if there are no issues. -->
<StackPanel Visibility="{x:Bind _stage.DiagnosticsViewModel.HasIssues, Converter={StaticResource VisibilityConverter}, Mode=OneWay}">
<TextBlock Visibility="{x:Bind _stage.DiagnosticsViewModel.HasIssues, Converter={StaticResource VisibilityConverter}, Mode=OneWay}"
FontWeight="Bold" Foreground="White" >
This Lottie has some issues ...
</TextBlock>
<ItemsControl Margin="0, 12, 12,12"
ItemsSource="{x:Bind _stage.DiagnosticsViewModel.Issues, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="lottie:Issue">
<StackPanel Orientation="Horizontal">
<TextBlock MinWidth="60">
<Hyperlink NavigateUri="{x:Bind Url}"><Run FontWeight="Bold"
Text="{x:Bind Code}" Foreground="Orange" /></Hyperlink>
</TextBlock>
<TextBlock Text="{x:Bind Description}" TextWrapping="Wrap" MaxWidth="240" Foreground="White"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!-- Separator -->
<Border Style="{StaticResource SeparatorStyle}" Visibility="{x:Bind _stage.DiagnosticsViewModel.HasIssues, Converter={StaticResource VisibilityConverter}, Mode=OneWay}"/>
<!-- Play speed controller. -->
<StackPanel>
<TextBlock FontWeight="Bold" >Play speed</TextBlock>
<Grid Margin="20,14,20,20">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Slider
Padding="10, 0, 10, 0"
HorizontalAlignment="Stretch"
LargeChange="1"
Maximum="2.0"
Minimum="-2.0"
SmallChange="0.1"
StepFrequency="0.1"
TickFrequency="0.5"
TickPlacement="TopLeft"
Value="{x:Bind _stage.Player.PlaybackRate, Mode=TwoWay}" />
<TextBlock Grid.Column="1" Margin="10,0,0,0">x <Run Text="{x:Bind _stage.Player.PlaybackRate, Mode=OneWay, Converter={StaticResource floatFormatter}}"/></TextBlock>
</Grid>
</StackPanel>
<!-- Separator -->
<Border Style="{StaticResource SeparatorStyle}"/>
<!-- Properties list. Only visible if there is a Diagnostics object. -->
<Grid Visibility="{x:Bind _stage.DiagnosticsViewModel.DiagnosticsObject, Converter={StaticResource VisibilityConverter}, ConverterParameter=whatthe, Mode=OneWay}">
<Grid.RowDefinitions>
<RowDefinition Height="26"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.Resources>
<DataTemplate x:Key="NormalTemplate" x:DataType="viewmodel:PairOfStrings">
<StackPanel Orientation="Horizontal" Margin="0,0,0,2">
<TextBlock Foreground="LightGray" MinWidth="60" Text="{x:Bind Item1}"/>
<TextBlock Foreground="White" Text="{x:Bind Item2}" TextWrapping="Wrap" MaxWidth="250" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="MarkerTemplate" x:DataType="viewmodel:Marker">
<StackPanel Orientation="Horizontal" Margin="0,0,0,2">
<TextBlock Foreground="LightGray" MinWidth="60" Text="{x:Bind PropertyName}"/>
<TextBlock Foreground="White" TextWrapping="Wrap" MaxWidth="250">
<Hyperlink Click="MarkerClick"><Run Text="{x:Bind ProgressText}"/></Hyperlink> <Run Text="{x:Bind Name}"/>
</TextBlock>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="MarkerWithDurationTemplate" x:DataType="viewmodel:MarkerWithDuration">
<StackPanel Orientation="Horizontal" Margin="0,0,0,2">
<TextBlock Foreground="LightGray" MinWidth="60" Text="{x:Bind PropertyName}"/>
<TextBlock Foreground="White" TextWrapping="Wrap" MaxWidth="250">
<Hyperlink Click="MarkerClick"><Run Text="{x:Bind ProgressText}"/></Hyperlink> -
<Hyperlink Click="MarkerEndClick"><Run Text="{x:Bind ToProgressText}"/></Hyperlink> <Run Text="{x:Bind Name}"/>
</TextBlock>
</StackPanel>
</DataTemplate>
<local:PropertiesTemplateSelector
x:Key="PropertiesTemplateSelector"
Normal="{StaticResource NormalTemplate}"
Marker="{StaticResource MarkerTemplate}"
MarkerWithDuration="{StaticResource MarkerWithDurationTemplate}"/>
</Grid.Resources>
<TextBlock FontWeight="Bold" >Lottie properties</TextBlock>
<ItemsControl x:Name="InfoList" Grid.Row="1" ItemsSource="{x:Bind PropertiesList}" ItemTemplateSelector="{StaticResource PropertiesTemplateSelector}"/>
</Grid>
<!-- Separator -->
<Border Style="{StaticResource SeparatorStyle}"
Visibility="{x:Bind _stage.DiagnosticsViewModel.DiagnosticsObject, Converter={StaticResource VisibilityConverter}, ConverterParameter=whatthe, Mode=OneWay}"/>
<!-- App version -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="26"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock FontWeight="Bold" >Lottie Viewer app</TextBlock>
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,2">
<TextBlock Foreground="LightGray" MinWidth="60" Text="Version"/>
<TextBlock Foreground="White" Text="{x:Bind AppVersion}" MaxWidth="250" />
</StackPanel>
</Grid>
</StackPanel>
</ScrollViewer>
</Grid>
</Grid>
<StackPanel x:Name="PlayerControls"
Width="340"
@ -183,8 +276,12 @@
<ColumnDefinition Width="3*"
MinWidth="200" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Filler so that when a tool (e.g. color picker) is showing, the play controls move to the left. -->
<Grid Grid.Column="3" Width="360" Visibility="{x:Bind IsControlPanelVisible, Converter={StaticResource VisibilityConverter}, Mode=OneWay}"/>
<RelativePanel Grid.Column="1">
<!-- Play/stop button -->
@ -195,14 +292,14 @@
RelativePanel.AlignBottomWithPanel="True"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignTopWithPanel="True"
Style="{StaticResource ControlsToggleButtonStyle}"
Style="{StaticResource ControlsToggleButtonPlainStyle}"
ToolTipService.ToolTip="Play/Stop"
AutomationProperties.Name="Toggle play/stop"
Unchecked="_playControl_Toggled">
<Grid>
<!-- Those TextBlocks are just for visual purposes, but are confusinng for accessibility,
so we remove them from UIA -->
<!-- Play -->
<TextBlock Visibility="{x:Bind _playStopButton.IsChecked, Converter={StaticResource VisibilityConverter}, ConverterParameter=not, Mode=OneWay}"
AutomationProperties.AccessibilityView="Raw">&#xedb5;</TextBlock>
@ -224,7 +321,7 @@
RelativePanel.AlignTopWithPanel="True"
RelativePanel.RightOf="_playStopButton"
ValueChanged="ProgressSliderChanged"
DiagnosticsObject="{x:Bind _stage.Player.Diagnostics, Mode=OneWay}" />
DiagnosticsViewModel="{x:Bind _stage.DiagnosticsViewModel, Mode=OneWay}" />
</RelativePanel>
</Grid>
</RelativePanel>

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

@ -4,81 +4,159 @@
//#define DebugDragDrop
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using Microsoft.Toolkit.Uwp.UI.Lottie;
using System.Threading.Tasks;
using LottieViewer.ViewModel;
using Windows.ApplicationModel;
using Windows.ApplicationModel.DataTransfer;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
#pragma warning disable SA1402 // File may only contain a single type
namespace LottieViewer
{
/// <summary>
/// MainPage.
/// </summary>
public sealed partial class MainPage : Page
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
readonly ToggleButton[] _controlPanelButtons;
int _playVersion;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public MainPage()
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
{
InitializeComponent();
// The control panel buttons. We hold onto these in order to ensure that no more
// than one is checked at the same time.
_controlPanelButtons = new[] { PaletteButton, InfoButton };
// Connect the player's progress to the scrubber's progress.
_scrubber.SetAnimatedCompositionObject(_stage.Player.ProgressObject);
// Add the background to the color picker so that it can be modified by the user.
_paletteColorPicker.PaletteEntries.Add(BackgroundColor);
// Get notified when info about the loaded Lottie changes.
_stage.DiagnosticsViewModel.PropertyChanged += DiagnosticsViewModel_PropertyChanged;
}
// Avoid "async void" method. Not valid here because we handle all async exceptions.
#pragma warning disable VSTHRD100
async void PickFile_Click(object sender, RoutedEventArgs e)
public ObservableCollection<object> PropertiesList { get; } = new ObservableCollection<object>();
public string AppVersion
{
#pragma warning restore VSTHRD100
var playVersion = ++_playVersion;
var filePicker = new FileOpenPicker
get
{
ViewMode = PickerViewMode.List,
SuggestedStartLocation = PickerLocationId.ComputerFolder,
};
filePicker.FileTypeFilter.Add(".json");
var version = Package.Current.Id.Version;
StorageFile file = null;
return string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, version.Revision);
}
}
void DiagnosticsViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var list = PropertiesList;
var viewModel = _stage.DiagnosticsViewModel;
if (viewModel is null)
{
list.Clear();
}
else if (e.PropertyName == nameof(viewModel.FileName))
{
list.Clear();
if (!string.IsNullOrWhiteSpace(viewModel.FileName))
{
list.Add(new PairOfStrings("File", viewModel.FileName));
}
// If the Lottie has 0 duration then it isn't valid, so don't show properties
// the only make sense for valid Lotties.
if (viewModel.LottieVisualDiagnostics?.Duration.Ticks > 0)
{
// Not all Lotties have a name, so only add the name if it exists.
if (!string.IsNullOrWhiteSpace(viewModel.Name))
{
list.Add(new PairOfStrings("Name", viewModel.Name));
}
list.Add(new PairOfStrings("Size", viewModel.SizeText));
list.Add(new PairOfStrings("Duration", viewModel.DurationText));
foreach (var marker in viewModel.Markers)
{
list.Add(marker);
}
}
}
}
internal ColorPaletteEntry BackgroundColor { get; } = new ColorPaletteEntry(Colors.White, "Background") { IsInitialColorSameAsColor = true };
void PickFile_Click(object sender, RoutedEventArgs e)
=> _ = OnPickFileAsync();
async Task OnPickFileAsync()
{
try
{
file = await filePicker.PickSingleFileAsync();
var playVersion = ++_playVersion;
var filePicker = new FileOpenPicker
{
ViewMode = PickerViewMode.List,
SuggestedStartLocation = PickerLocationId.ComputerFolder,
};
filePicker.FileTypeFilter.Add(".json");
StorageFile file = null;
try
{
file = await filePicker.PickSingleFileAsync();
}
catch
{
// Ignore PickSingleFileAsync exceptions so they don't crash the process.
}
if (file == null)
{
// Used declined to pick anything.
return;
}
if (playVersion != _playVersion)
{
return;
}
// Reset the scrubber to the 0 position.
_scrubber.Value = 0;
// If we were stopped in manual play control, turn it back to automatic.
if (!_playStopButton.IsChecked.Value)
{
_playStopButton.IsChecked = true;
}
_stage.DoDragDropped(file);
}
catch
finally
{
// Ignore PickSingleFileAsync exceptions so they don't crash the process.
// Uncheck the button. The button is a ToggleButton so that it indicates
// visually when the file picker is open. We need to manually reset its state.
PickFile.IsChecked = false;
}
if (file == null)
{
// Used declined to pick anything.
return;
}
if (playVersion != _playVersion)
{
return;
}
// Reset the scrubber to the 0 position.
_scrubber.Value = 0;
// If we were stopped in manual play control, turn it back to automatic.
if (!_playStopButton.IsChecked.Value)
{
_playStopButton.IsChecked = true;
}
_stage.DoDragDropped(file);
}
// Avoid "async void" method. Not valid here because we handle all async exceptions.
@ -176,6 +254,8 @@ namespace LottieViewer
bool _ignoreScrubberValueChanges;
public event PropertyChangedEventHandler PropertyChanged;
void ProgressSliderChanged(object sender, ScrubberValueChangedEventArgs e)
{
if (!_ignoreScrubberValueChanges)
@ -229,34 +309,88 @@ namespace LottieViewer
void CopyIssuesToClipboard(object sender, RoutedEventArgs e)
{
var issues = _stage.Diagnostics.PlayerIssues;
var issues = _stage.DiagnosticsViewModel.Issues;
var dataPackage = new DataPackage();
dataPackage.RequestedOperation = DataPackageOperation.Copy;
dataPackage.SetText(string.Join("\r\n", issues.Select(iss => iss.ToString())));
Clipboard.SetContent(dataPackage);
Clipboard.Flush();
}
// Uncheck all the other control panel buttons when one is checked.
// This allows toggle buttons to act like radio buttons.
void ControlPanelButtonChecked(object sender, RoutedEventArgs e)
{
foreach (var button in _controlPanelButtons)
{
if (button != sender)
{
button.IsChecked = false;
}
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsControlPanelVisible)));
}
public bool IsControlPanelVisible => _controlPanelButtons.Any(b => b.IsChecked == true);
// When one of the control panel buttons is unchecked, if all the buttons
// are now unpressed, remove the filler from the play/stop control bar so that
// the scrubber takes up the whole area.
void ControlPanelButtonUnchecked(object sender, RoutedEventArgs e)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsControlPanelVisible)));
}
// Called when the user clicks on a marker hyperlink.
void MarkerClick(Windows.UI.Xaml.Documents.Hyperlink sender, Windows.UI.Xaml.Documents.HyperlinkClickEventArgs args)
{
var dataContext = ((FrameworkElement)sender.ElementStart.Parent).DataContext;
var marker = (Marker)dataContext;
// Ensure the Play button is unchecked because SetProgress will stop playing.
_playStopButton.IsChecked = false;
// Set the progress to the marker value.
_stage.Player.SetProgress(marker.Progress);
}
// Called when the user clicks on a marker-with-duration hyperlink.
void MarkerEndClick(Windows.UI.Xaml.Documents.Hyperlink sender, Windows.UI.Xaml.Documents.HyperlinkClickEventArgs args)
{
var dataContext = ((FrameworkElement)sender.ElementStart.Parent).DataContext;
var marker = (MarkerWithDuration)dataContext;
// Ensure the Play button is unchecked because SetProgress will stop playing.
_playStopButton.IsChecked = false;
// Set the progress to the marker value.
_stage.Player.SetProgress(marker.ToProgress);
}
}
#pragma warning disable SA1402 // File may only contain a single type
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
// Converts bool and null values into Visibility values.
public sealed class VisiblityConverter : IValueConverter
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
#pragma warning restore SA1402 // File may only contain a single type
{
object IValueConverter.Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool boolValue)
{
if ((string)parameter == "not")
{
boolValue = !boolValue;
}
return boolValue ? Visibility.Visible : Visibility.Collapsed;
// The value is already a boolean.
}
else
{
// The value is not a boolean. Used !null to convert to a boolean.
boolValue = !(value is null);
}
return null;
// The "not" parameter inverts the logic.
if ((string)parameter == "not")
{
boolValue = !boolValue;
}
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, string language)
@ -266,11 +400,7 @@ namespace LottieViewer
}
}
#pragma warning disable SA1402 // File may only contain a single type
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public sealed class FloatFormatter : IValueConverter
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
#pragma warning restore SA1402 // File may only contain a single type
{
object IValueConverter.Convert(object value, Type targetType, object parameter, string language)
{
@ -283,4 +413,29 @@ namespace LottieViewer
throw new NotImplementedException();
}
}
public sealed class PropertiesTemplateSelector : DataTemplateSelector
{
public DataTemplate Normal { get; set; }
public DataTemplate Marker { get; set; }
public DataTemplate MarkerWithDuration { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (item is PairOfStrings)
{
return Normal;
}
else if (item is Marker)
{
return Marker;
}
else
{
return MarkerWithDuration;
}
}
}
}

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

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" IgnorableNamespaces="uap mp">
<Identity Name="8e772a41-9ca3-4339-a6a1-093cb9e19f2d" Publisher="CN=developer" Version="1.0.35.0" />
<Identity Name="8e772a41-9ca3-4339-a6a1-093cb9e19f2d" Publisher="CN=9AF86C33-622F-4896-9ED2-78CAE82263F6" Version="1.0.37.0" />
<mp:PhoneIdentity PhoneProductId="8e772a41-9ca3-4339-a6a1-093cb9e19f2d" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Properties>
<DisplayName>Lottie Viewer</DisplayName>

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

@ -0,0 +1,240 @@
<UserControl
x:Class="LottieViewer.PaletteColorPicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:LottieViewer"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Windows.UI.Xaml.Controls" xmlns:primitives="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
mc:Ignorable="d"
d:DesignHeight="900"
d:DesignWidth="400">
<UserControl.Resources>
<SolidColorBrush x:Key="ListBoxItemBorderBrush" Color="#C8C8C8"/>
<SolidColorBrush x:Key="ForegroundBrush" Color="#EEEEEE"/>
<!-- ListBox text uses this color on the selected item. -->
<SolidColorBrush x:Key="ForegroundSelectedBrush" Color="#222222"/>
<SolidColorBrush x:Key="SelectedFillBrush" Color="#F4F4F4"/>
</UserControl.Resources>
<Grid Background="{StaticResource DropTargetBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<muxc:ListBox
x:Name="_listBox"
Grid.Row="1"
Background="Transparent"
ItemsSource="{x:Bind PaletteEntries}"
SelectionMode="Single"
SelectionChanged="PaletteListBox_SelectionChanged" DoubleTapped="PaletteListBox_DoubleTapped">
<muxc:ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<Style x:Key="BaseContentPresenterStyle" TargetType="ContentPresenter">
<Setter Property="FontFamily" Value="XamlAutoFontFamily" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="LineStackingStrategy" Value="MaxHeight" />
<Setter Property="TextLineBounds" Value="Full" />
<Setter Property="OpticalMarginAlignment" Value="TrimSideBearings" />
</Style>
<Style x:Key="BodyContentPresenterStyle" TargetType="ContentPresenter" BasedOn="{StaticResource BaseContentPresenterStyle}">
<Setter Property="FontWeight" Value="Normal" />
</Style>
</Grid.Resources>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" >
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ForegroundBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PressedBackground" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource LottieBasicBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ForegroundBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PressedBackground" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource LottieBasicBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ForegroundBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Selected">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PressedBackground" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource LottieBasicBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ForegroundSelectedBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="SelectedUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PressedBackground" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SelectedFillBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ForegroundSelectedBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="SelectedPointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PressedBackground" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SelectedFillBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ForegroundSelectedBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="SelectedPressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PressedBackground" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource LottieBasicBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ForegroundSelectedBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Rectangle x:Name="PressedBackground" Fill="Transparent" Control.IsTemplateFocusTarget="True" />
<!-- Presents each ListBoxItem. -->
<ContentPresenter x:Name="ContentPresenter"
BorderBrush="{StaticResource ListBoxItemBorderBrush}" BorderThickness="0,1,0,1"
Foreground="{StaticResource ForegroundBrush}"
Content="{TemplateBinding Content}"
ContentTransitions="{TemplateBinding ContentTransitions}"
ContentTemplate="{TemplateBinding ContentTemplate}"
TextWrapping="NoWrap"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</muxc:ListBox.Resources>
<muxc:ItemsControl.ItemTemplate>
<DataTemplate x:DataType="local:ColorPaletteEntry">
<Grid Padding="8" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="210"/>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="0"/>
<ColumnDefinition Width="40"/>
</Grid.ColumnDefinitions>
<muxc:TextBlock Text="{x:Bind Name}" Margin="10,0,0,0"/>
<Border Grid.Column="1" BorderBrush="#DDDDDD" BorderThickness="3,3,0,3">
<Rectangle
HorizontalAlignment="Stretch"
Height="20">
<Rectangle.Fill>
<SolidColorBrush Color="{x:Bind InitialColor, Mode=OneWay}"/>
</Rectangle.Fill>
</Rectangle>
</Border>
<Border Grid.Column="2" BorderBrush="#DDDDDD" BorderThickness="0,3,0,3">
<Rectangle
HorizontalAlignment="Stretch"
Height="20">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="{x:Bind InitialColor, Mode=OneWay}" Offset="0" />
<GradientStop Color="{x:Bind Color, Mode=OneWay}" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Border>
<Border Grid.Column="3" BorderBrush="#DDDDDD" BorderThickness="0,3,3,3">
<Rectangle
HorizontalAlignment="Stretch"
Height="20">
<Rectangle.Fill>
<SolidColorBrush Color="{x:Bind Color, Mode=OneWay}"/>
</Rectangle.Fill>
</Rectangle>
</Border>
</Grid>
</DataTemplate>
</muxc:ItemsControl.ItemTemplate>
</muxc:ListBox>
<StackPanel>
<Border Margin="30,20,-20,0">
<muxc:ColorPicker
x:Name="MyColorPicker"
IsEnabled="False"
ColorChanged="MyColorPicker_ColorChanged"
ColorSpectrumComponents="HueSaturation"
ColorSpectrumShape="Ring"
IsColorChannelTextInputVisible="False"
IsColorPreviewVisible="False"
IsHexInputVisible="False"
IsAlphaTextInputVisible="False"
IsAlphaSliderVisible="False" >
<muxc:ColorPicker.RenderTransform>
<ScaleTransform ScaleX="0.85" ScaleY="0.85"/>
</muxc:ColorPicker.RenderTransform>
</muxc:ColorPicker>
</Border>
<!-- Use a second ColorPicker so that we can show the text entry
box in the middle instead of off to the left. -->
<muxc:ColorPicker
x:Name="TextColorPicker"
ColorChanged="TextColorPicker_ColorChanged"
HorizontalAlignment="Center"
Margin="0,-75,-175,0"
ColorSpectrumComponents="HueSaturation"
ColorSpectrumShape="Ring"
IsColorChannelTextInputVisible="False"
IsColorPreviewVisible="False"
IsColorSpectrumVisible="False"
IsColorSliderVisible="False"
IsHexInputVisible="True"
IsAlphaTextInputVisible="False"
IsAlphaSliderVisible="False" >
</muxc:ColorPicker>
<StackPanel Margin="20,-15,20,20">
<!-- Separator -->
<Border Style="{StaticResource SeparatorStyle}"/>
<TextBlock FontWeight="Bold">Colors</TextBlock>
</StackPanel>
</StackPanel>
</Grid>
</UserControl>

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

@ -0,0 +1,193 @@
// 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.ObjectModel;
using System.Collections.Specialized;
using System.Numerics;
using LottieViewer.ViewModel;
using Microsoft.Toolkit.Uwp.UI.Lottie.CompMetadata;
using Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData.MetaData;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
namespace LottieViewer
{
/// <summary>
/// Displays a color picker for multiple colors in a palette.
/// </summary>
public sealed partial class PaletteColorPicker : UserControl
{
LottieVisualDiagnosticsViewModel _diagnosticsViewModel;
// Used to prevent infinite recursion when the color picker is updated.
// Needed because we have 2-way binding between 2 color pickers and they
// try to set each others values.
bool m_isColorPickerChanging = false;
public PaletteColorPicker()
{
this.InitializeComponent();
PaletteEntries.CollectionChanged += PaletteEntries_CollectionChanged;
}
internal LottieVisualDiagnosticsViewModel DiagnosticsViewModel
{
get => _diagnosticsViewModel;
set
{
if (_diagnosticsViewModel != null)
{
// Unhook form the previous DiagnosticsViewModel.
value.ThemePropertyBindings.CollectionChanged -= Value_CollectionChanged;
}
_diagnosticsViewModel = value;
if (_diagnosticsViewModel != null)
{
value.ThemePropertyBindings.CollectionChanged += Value_CollectionChanged;
}
}
}
public ObservableCollection<ColorPaletteEntry> PaletteEntries { get; } = new ObservableCollection<ColorPaletteEntry>();
void PaletteEntries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
// These are the only cases we expect becasue of the way we modify the collection.
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Remove:
break;
// These are never expected because of the way we modify the collection.
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Reset:
default:
throw new InvalidOperationException();
}
// Ensure something is selected if there are any items in the list.
if ((_listBox.SelectedIndex == -1 || _listBox.SelectedIndex >= PaletteEntries.Count)
&& PaletteEntries.Count > 0)
{
_listBox.SelectedIndex = PaletteEntries.Count - 1;
}
}
void Value_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
// Add the entry to the list, and hook it up so that changing the entry will update
// the entry in the theming property set.
foreach (PropertyBinding item in e.NewItems)
{
if (item.ExposedType == PropertySetValueType.Color)
{
var color = (Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData.Wui.Color)item.DefaultValue;
var entry = new ColorPaletteEntry(Color.FromArgb(color.A, color.R, color.G, color.B), item.DisplayName);
PaletteEntries.Add(entry);
entry.PropertyChanged += (_, args) =>
{
var newColor = entry.Color;
_diagnosticsViewModel.ThemingPropertySet.InsertVector4(item.BindingName, ColorAsVector4(entry.Color));
};
}
}
break;
case NotifyCollectionChangedAction.Reset:
// Remove all except the first item in PaletteEntries (first item is Background).
while (PaletteEntries.Count > 1)
{
PaletteEntries.Remove(PaletteEntries[PaletteEntries.Count - 1]);
}
break;
// These are all unexpected. Don't try to handle them.
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Move:
default:
throw new InvalidOperationException();
}
}
// Synchronizes the color picker's color with the selected item in the list.
void PaletteListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_listBox.SelectedItem is ColorPaletteEntry selectedEntry)
{
MyColorPicker.Color = selectedEntry.Color;
MyColorPicker.IsEnabled = true;
}
else
{
MyColorPicker.Color = Color.FromArgb(0, 0, 0, 0);
MyColorPicker.IsEnabled = false;
}
}
void MyColorPicker_ColorChanged(ColorPicker sender, ColorChangedEventArgs args)
{
if (m_isColorPickerChanging)
{
// Ignore if we're in the middle of changing the color already.
return;
}
if (_listBox.SelectedItem is ColorPaletteEntry selectedEntry)
{
m_isColorPickerChanging = true;
selectedEntry.Color = args.NewColor;
TextColorPicker.Color = args.NewColor;
m_isColorPickerChanging = false;
}
}
void TextColorPicker_ColorChanged(ColorPicker sender, ColorChangedEventArgs args)
{
// Update the main color picker.
MyColorPicker.Color = args.NewColor;
}
// Handle double-click on an entry. Restore the original color.
void PaletteListBox_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
if (GetDataContext((DependencyObject)e.OriginalSource) is ColorPaletteEntry colorPaletteEntry)
{
// Reset the color to the original color.
colorPaletteEntry.Color = colorPaletteEntry.InitialColor;
MyColorPicker.Color = colorPaletteEntry.Color;
}
// Search up the tree for an object with a data context, and returns
// the data context.
object GetDataContext(DependencyObject obj)
{
if (obj is FrameworkElement fe && fe.DataContext != null)
{
return fe.DataContext;
}
else
{
return obj is null ? null : GetDataContext(VisualTreeHelper.GetParent(obj));
}
}
}
// Converts a color to the Vector4 representation used in a CompositionPropertySet for
// color binding.
static Vector4 ColorAsVector4(Color color) => new Vector4(color.R, color.G, color.B, color.A);
}
}

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

@ -5,7 +5,7 @@
using System;
using System.Collections.Specialized;
using System.Numerics;
using Microsoft.Toolkit.Uwp.UI.Lottie;
using LottieViewer.ViewModel;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Composition;
@ -48,21 +48,31 @@ namespace LottieViewer
readonly CompositionColorBrush _decreaseRectangleBrush;
readonly SolidColorBrush _markerBrush;
readonly LottieVisualDiagnosticsViewModel _diagnostics = new LottieVisualDiagnosticsViewModel();
LottieVisualDiagnosticsViewModel _diagnostics;
string _currentVisualStateName;
public static readonly DependencyProperty DiagnosticsObjectProperty =
DependencyProperty.Register("DiagnosticsObject", typeof(object), typeof(Scrubber), new PropertyMetadata(null, OnDiagnosticsObjectChanged));
public event TypedEventHandler<Scrubber, ScrubberValueChangedEventArgs> ValueChanged;
internal LottieVisualDiagnosticsViewModel DiagnosticsViewModel
{
get => _diagnostics;
set
{
if (_diagnostics != null)
{
_diagnostics.Markers.CollectionChanged -= Markers_CollectionChanged;
}
_diagnostics = value;
_diagnostics.Markers.CollectionChanged += Markers_CollectionChanged;
}
}
public Scrubber()
{
this.InitializeComponent();
_diagnostics.Markers.CollectionChanged += Markers_CollectionChanged;
// Create the brush used for markers.
_markerBrush = new SolidColorBrush(GetResourceBrushColor("LottieBasicBrush"));
@ -144,12 +154,6 @@ namespace LottieViewer
set => _slider.Value = value;
}
public object DiagnosticsObject
{
get { return (object)GetValue(DiagnosticsObjectProperty); }
set { SetValue(DiagnosticsObjectProperty, value); }
}
// Associates the given CompositionObject with the scrubber. The object is required
// to have a property called "Progress" that the scrubber position will be bound to.
internal void SetAnimatedCompositionObject(CompositionObject obj)
@ -172,12 +176,6 @@ namespace LottieViewer
_thumb.StartAnimation("Offset.X", thumbPositionAnimation);
}
static void OnDiagnosticsObjectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var me = (Scrubber)d;
me._diagnostics.DiagnosticsObject = (LottieVisualDiagnostics)e.NewValue;
}
protected override Size ArrangeOverride(Size finalSize)
{
// Arrange the elements. This has to be done before asking the
@ -201,7 +199,7 @@ namespace LottieViewer
{
var topRect = (Rectangle)_markersTop.Children[i];
var bottomRect = (Rectangle)_markersBottom.Children[i];
var offset = _diagnostics.Markers[i].Offset;
var offset = _diagnostics.Markers[i].ConstrainedProgress;
topRect.Margin = new Thickness((offset * barWidth) + c_trackMargin, 0, 0, 0);
bottomRect.Margin = new Thickness((offset * barWidth) + c_trackMargin, 0, 0, 0);
}

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

@ -5,14 +5,15 @@
xmlns:local="using:LottieViewer"
xmlns:lottie="using:Microsoft.Toolkit.Uwp.UI.Lottie"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:viewmodel="using:LottieViewer.ViewModel"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d">
<UserControl.Resources>
<local:LottieVisualDiagnosticsViewModel x:Name="_diagnostics" DiagnosticsObject="{x:Bind _player.Diagnostics, Mode=OneWay}"/>
<viewmodel:LottieVisualDiagnosticsViewModel x:Name="_diagnosticsViewModel" DiagnosticsObject="{x:Bind _player.Diagnostics, Mode=OneWay}"/>
</UserControl.Resources>
<Grid>
<Grid>
<Grid.RowDefinitions>
@ -52,7 +53,7 @@
Stretch="Uniform">
<Border>
<Border.Background>
<SolidColorBrush Color="{x:Bind Path=ArtboardColor, Mode=OneWay}" />
<SolidColorBrush Color="{x:Bind ArtboardColor, Mode=OneWay}" />
</Border.Background>
<!-- Stretch="None" so that the Border will have the same shape as the Lottie. -->
<muxc:AnimatedVisualPlayer x:Name="_player"
@ -65,13 +66,5 @@
</Border>
</Viewbox>
</Grid>
<StackPanel Margin="24"
VerticalAlignment="Bottom">
<TextBlock Text="{x:Bind Diagnostics.FileName, Mode=OneWay}" />
<TextBlock Text="{x:Bind Diagnostics.SizeText, Mode=OneWay}" />
<TextBlock Text="{x:Bind Diagnostics.DurationText, Mode=OneWay}" />
<TextBlock Text="{x:Bind Diagnostics.MarkersText, Mode=OneWay}" />
</StackPanel>
</Grid>
</UserControl>

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

@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.Toolkit.Uwp.UI.Lottie;
using LottieViewer.ViewModel;
using Microsoft.UI.Xaml.Controls;
using Windows.Storage;
using Windows.UI;
@ -12,15 +12,19 @@ using Windows.UI.Xaml.Controls;
namespace LottieViewer
{
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
/// <summary>
/// Stage.
/// This is where the Lottie file is displayed. This is a wrapper around the
/// AnimatedVisualPlayer that plays a loading animation and exposes the
/// diagnostics object as a view model.
/// </summary>
public sealed partial class Stage : UserControl
{
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
// The color of the artboard is a dependency property so that it can be the
// target of binding.
public static readonly DependencyProperty ArtboardColorProperty =
DependencyProperty.Register(nameof(ArtboardColor), typeof(Color), typeof(Stage), new PropertyMetadata(Colors.White));
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
DependencyProperty.Register("ArtboardColor", typeof(Color), typeof(Stage), new PropertyMetadata(Colors.Black));
public Stage()
{
@ -29,18 +33,17 @@ namespace LottieViewer
Reset();
}
internal LottieVisualDiagnosticsViewModel Diagnostics => _diagnostics;
// The DiagnosticsViewModel contains information about the currently playing
// Lottie file. This information is consumed by other controls such as the
// color picker and scrubber.
internal LottieVisualDiagnosticsViewModel DiagnosticsViewModel => _diagnosticsViewModel;
internal AnimatedVisualPlayer Player => _player;
internal LottieVisualSource Source => _playerSource;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public Color ArtboardColor
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
{
get => (Color)GetValue(ArtboardColorProperty);
set => SetValue(ArtboardColorProperty, value);
get { return (Color)GetValue(ArtboardColorProperty); }
set { SetValue(ArtboardColorProperty, value); }
}
// Avoid "async void" method. Not valid here because we handle all async exceptions.
@ -58,7 +61,7 @@ namespace LottieViewer
try
{
// Load the Lottie composition.
await Source.SetSourceAsync(file);
await _playerSource.SetSourceAsync(file);
}
catch (Exception)
{
@ -82,7 +85,7 @@ namespace LottieViewer
_player.Opacity = 1;
try
{
await Player.PlayAsync(0, 1, true);
await _player.PlayAsync(0, 1, true);
}
catch
{

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

@ -7,8 +7,9 @@ using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using Microsoft.Toolkit.Uwp.UI.Lottie;
using Microsoft.Toolkit.Uwp.UI.Lottie.CompMetadata;
namespace LottieViewer
namespace LottieViewer.ViewModel
{
/// <summary>
/// View model class for <see cref="LottieVisualDiagnostics"/>.
@ -24,22 +25,70 @@ namespace LottieViewer
set
{
LottieVisualDiagnostics = (LottieVisualDiagnostics)value;
PlayerIssues.Clear();
Issues.Clear();
Markers.Clear();
ThemePropertyBindings.Clear();
ThemingPropertySet = null;
if (value != null)
{
// Populate the issues list.
foreach (var issue in LottieVisualDiagnostics.JsonParsingIssues.
Concat(LottieVisualDiagnostics.LottieValidationIssues).
Concat(LottieVisualDiagnostics.TranslationIssues).
OrderBy(a => a.Code).
ThenBy(a => a.Description))
{
PlayerIssues.Add(issue);
Issues.Add(issue);
}
foreach (var marker in LottieVisualDiagnostics.Markers)
// Populate the marker info.
var composition = LottieVisualDiagnostics.LottieComposition;
if (composition != null)
{
Markers.Add((marker.Key, marker.Value));
var framesPerSecond = composition.FramesPerSecond;
var duration = composition.Duration.TotalSeconds;
var totalFrames = framesPerSecond * duration;
var isFirst = true;
foreach (var m in composition.Markers)
{
var progress = m.Frame / totalFrames;
Marker marker;
if (m.DurationInFrames == 0)
{
marker = new Marker
{
ProgressText = $"{progress:0.000#}",
};
}
else
{
var toProgress = progress + (m.DurationInFrames / totalFrames);
marker = new MarkerWithDuration
{
ProgressText = $"{progress:0.000#}",
ToProgress = toProgress,
ToProgressText = $"{toProgress:0.000#}",
};
}
marker.PropertyName = isFirst ? $"Marker{(composition.Markers.Count > 1 ? "s" : string.Empty)}" : string.Empty;
isFirst = false;
marker.Name = m.Name;
marker.Progress = progress;
Markers.Add(marker);
}
}
ThemingPropertySet = LottieVisualDiagnostics.ThemingPropertySet;
if (LottieVisualDiagnostics.ThemePropertyBindings != null)
{
foreach (var binding in LottieVisualDiagnostics.ThemePropertyBindings)
{
ThemePropertyBindings.Add(binding);
}
}
}
@ -48,27 +97,47 @@ namespace LottieViewer
{
propertyChangedCallback(this, new PropertyChangedEventArgs(nameof(DurationText)));
propertyChangedCallback(this, new PropertyChangedEventArgs(nameof(FileName)));
propertyChangedCallback(this, new PropertyChangedEventArgs(nameof(MarkersText)));
propertyChangedCallback(this, new PropertyChangedEventArgs(nameof(PlayerHasIssues)));
propertyChangedCallback(this, new PropertyChangedEventArgs(nameof(HasIssues)));
propertyChangedCallback(this, new PropertyChangedEventArgs(nameof(LottieVisualDiagnostics)));
propertyChangedCallback(this, new PropertyChangedEventArgs(nameof(Name)));
propertyChangedCallback(this, new PropertyChangedEventArgs(nameof(SizeText)));
propertyChangedCallback(this, new PropertyChangedEventArgs(nameof(ThemingPropertySet)));
propertyChangedCallback(this, new PropertyChangedEventArgs(nameof(DiagnosticsObject)));
}
}
}
public LottieVisualDiagnostics LottieVisualDiagnostics { get; private set; }
public string DurationText => LottieVisualDiagnostics is null ? string.Empty : $"{LottieVisualDiagnostics.Duration.TotalSeconds} secs";
public string DurationText
{
get
{
if (LottieVisualDiagnostics is null)
{
return string.Empty;
}
else
{
var seconds = LottieVisualDiagnostics.Duration.TotalSeconds;
return $"{seconds:0.##} second{(seconds == 1 ? string.Empty : "s")}";
}
}
}
public string Name => LottieVisualDiagnostics?.LottieComposition?.Name ?? string.Empty;
public string FileName => LottieVisualDiagnostics?.FileName ?? string.Empty;
public ObservableCollection<(string Name, double Offset)> Markers { get; } = new ObservableCollection<(string, double)>();
public ObservableCollection<Marker> Markers { get; } = new ObservableCollection<Marker>();
public string MarkersText =>
LottieVisualDiagnostics is null ? string.Empty : string.Join(", ", Markers.Select(value => $"{value.Name}={value.Offset:0.###}"));
public ObservableCollection<PropertyBinding> ThemePropertyBindings { get; } = new ObservableCollection<PropertyBinding>();
public bool PlayerHasIssues => PlayerIssues.Count > 0;
public Windows.UI.Composition.CompositionPropertySet ThemingPropertySet { get; private set; }
public ObservableCollection<Issue> PlayerIssues { get; } = new ObservableCollection<Issue>();
public bool HasIssues => Issues.Count > 0;
public ObservableCollection<Issue> Issues { get; } = new ObservableCollection<Issue>();
public string SizeText
{

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

@ -0,0 +1,21 @@
// 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 LottieViewer.ViewModel
{
class Marker
{
public string PropertyName { get; set; }
public string Name { get; set; }
public double Progress { get; set; }
public string ProgressText { get; set; }
public double ConstrainedProgress => Math.Max(0, Math.Min(1, Progress));
}
}

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

@ -0,0 +1,18 @@
// 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 LottieViewer.ViewModel
{
// A marker that has a non-0 duration.
sealed class MarkerWithDuration : Marker
{
public double ToProgress { get; set; }
public string ToProgressText { get; set; }
public double ConstrainedToProgress => Math.Max(0, Math.Min(1, ToProgress));
}
}

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

@ -0,0 +1,17 @@
// 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.
namespace LottieViewer.ViewModel
{
// An non-generic alternative to Tuple<string, string>.
// Needed because XAML doesn't support generic type names.
sealed class PairOfStrings
{
internal PairOfStrings(string item1, string item2) => (Item1, Item2) = (item1, item2);
public string Item1 { get; }
public string Item2 { get; }
}
}

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

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<Configurations>Debug;Release</Configurations>
<LangVersion>latest</LangVersion>
<DefineConstants>PUBLIC_CompMetadata</DefineConstants>
</PropertyGroup>
<Import Project="..\..\source\CompMetadata\\CompMetadata.projitems" Label="Shared" />
<ItemGroup>
<ProjectReference Include="..\WinCompData\WinCompData.dll.csproj" />
</ItemGroup>
</Project>

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

@ -12,6 +12,7 @@
<Import Project="..\..\source\LottieMetadata\LottieMetadata.projitems" Label="Shared" />
<ItemGroup>
<ProjectReference Include="..\CompMetadata\CompMetadata.dll.csproj" />
<ProjectReference Include="..\LottieData\LottieData.dll.csproj" />
<ProjectReference Include="..\WinCompData\WinCompData.dll.csproj" />
<ProjectReference Include="..\WinStorageStreamsData\WinStorageStreamsData.dll.csproj" />

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

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
@ -10,6 +10,7 @@
<Import Project="..\..\source\UIDataCodeGen\UIDataCodeGen.projitems" Label="Shared" />
<ItemGroup>
<ProjectReference Include="..\CompMetadata\CompMetadata.dll.csproj" />
<ProjectReference Include="..\LottieMetadata\LottieMetadata.dll.csproj" />
<ProjectReference Include="..\UIData\UIData.dll.csproj" />
</ItemGroup>

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

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>b0197c19-bdf5-473e-a022-e21f6122eee5</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>CompMetadata</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)PropertyBinding.cs" />
</ItemGroup>
</Project>

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

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>b0197c19-bdf5-473e-a022-e21f6122eee5</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<PropertyGroup />
<Import Project="CompMetadata.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>

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

@ -0,0 +1,45 @@
// 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.Lottie.WinCompData.MetaData;
namespace Microsoft.Toolkit.Uwp.UI.Lottie.CompMetadata
{
/// <summary>
/// Describes a name bound to a value in a CompositionPropertySet.
/// </summary>
#if PUBLIC_CompMetadata
public
#endif
sealed class PropertyBinding
{
/// <summary>
/// The name used to identify the value in the CompositionPropertySet.
/// </summary>
public string BindingName { get; set; }
/// <summary>
/// A name for the binding for display in tools.
/// </summary>
public string DisplayName { get; set; }
/// <summary>
/// The type of data stored in the CompositionPropertySet under this name.
/// </summary>
public PropertySetValueType ActualType { get; set; }
/// <summary>
/// The type that should be used when making this binding available via an API.
/// Typically this is the same as the <see cref="ActualType"/>, however some types
/// are not supported by animations expressions and must be stored using a different type,
/// for example, colors are stored as Vector4.
/// </summary>
public PropertySetValueType ExposedType { get; set; }
/// <summary>
/// The default value of the binding.
/// </summary>
public object DefaultValue { get; set; }
}
}

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

@ -4,6 +4,8 @@
using System;
using System.Diagnostics;
using System.Linq;
using Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools;
using Microsoft.UI.Xaml.Controls;
using Windows.UI.Composition;
@ -19,6 +21,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie
internal static readonly ContentFactory FailedContent = new ContentFactory(null);
readonly LottieVisualDiagnostics _diagnostics;
WinCompData.Visual _wincompDataRootVisual;
WinCompData.CompositionPropertySet _wincompDataThemingPropertySet;
CompositionPropertySet _themingPropertySet;
double _width;
double _height;
TimeSpan _duration;
@ -37,13 +41,23 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie
internal void SetRootVisual(WinCompData.Visual rootVisual)
{
// Save the root visual.
_wincompDataRootVisual = rootVisual;
// Find the theming property set, if any.
var graph = ObjectGraph<Graph.Node>.FromCompositionObject(_wincompDataRootVisual, includeVertices: false);
_wincompDataThemingPropertySet = graph.
CompositionObjectNodes.
Where(n => n.Object is WinCompData.CompositionPropertySet cps && cps.Owner is null).
Select(n => (WinCompData.CompositionPropertySet)n.Object).FirstOrDefault();
}
internal bool CanInstantiate => _wincompDataRootVisual != null;
public IAnimatedVisual TryCreateAnimatedVisual(Compositor compositor, out object diagnostics)
{
// Clone the Diagnostics object so that the data from the translation is captured, then we
// will update the clone with information about this particular instantiation.
var diags = _diagnostics?.Clone();
diagnostics = diags;
@ -54,15 +68,25 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie
else
{
var sw = Stopwatch.StartNew();
var instantiator = new Instantiator(compositor);
var result = new DisposableAnimatedVisual()
{
RootVisual = Instantiator.CreateVisual(compositor, _wincompDataRootVisual),
RootVisual = (Visual)instantiator.GetInstance(_wincompDataRootVisual),
Size = new System.Numerics.Vector2((float)_width, (float)_height),
Duration = _duration,
};
if (diags != null)
{
if (_wincompDataThemingPropertySet != null && _themingPropertySet is null)
{
// Instantiate the theming property set. This is shared by all of the instantiations.
_themingPropertySet = (CompositionPropertySet)instantiator.GetInstance(_wincompDataThemingPropertySet);
diags.ThemingPropertySet = _diagnostics.ThemingPropertySet = _themingPropertySet;
}
diags.InstantiationTime = sw.Elapsed;
}

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

@ -34,25 +34,16 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie
readonly Wc.ExpressionAnimation _expressionAnimation;
#endif
Instantiator(Wc.Compositor compositor)
public Instantiator(Wc.Compositor compositor)
{
_c = compositor;
#if ReuseExpressionAnimation
_expressionAnimation = _c.CreateExpressionAnimation();
#endif
}
/// <summary>
/// Creates a new instance of <see cref="Windows.UI.Composition.Visual"/>
/// described by the given <see cref="WinCompData.Visual"/>.
/// </summary>
/// <returns>The <see cref="Windows.UI.Composition.Visual"/>.</returns>
internal static Wc.Visual CreateVisual(Wc.Compositor compositor, Wd.Visual visual)
{
var converter = new Instantiator(compositor);
var result = converter.GetVisual(visual);
return result;
}
public Wc.CompositionObject GetInstance(Wd.CompositionObject obj) => GetCompositionObject(obj);
bool GetExisting<T>(object key, out T result)
{

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

@ -13,6 +13,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp.UI.Lottie.CompMetadata;
using Microsoft.Toolkit.Uwp.UI.Lottie.LottieData;
using Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization;
using Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp;
@ -29,6 +30,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie
/// </summary>
abstract class Loader
{
// Identifies the bound property names in SourceMetadata.
static readonly Guid s_propertyBindingNamesKey = new Guid("A115C46A-254C-43E6-A3C7-9DE516C3C3C8");
// Private constructor prevents subclassing outside of this class.
Loader()
{
@ -98,13 +102,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie
// code can be derived from it.
diagnostics.LottieComposition = lottieComposition;
// Create the marker info.
diagnostics.Markers =
lottieComposition.Markers.Select(m =>
new KeyValuePair<string, double>(
m.Name,
m.Frame / (lottieComposition.FramesPerSecond * lottieComposition.Duration.TotalSeconds))).ToArray();
// Validate the composition and report if issues are found.
diagnostics.LottieValidationIssues = ToIssues(LottieCompositionValidator.Validate(lottieComposition));
diagnostics.ValidationTime = timeMeasurer.GetElapsedAndRestart();
@ -123,14 +120,17 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie
TranslationResult translationResult;
await CheckedAwaitAsync(Task.Run(() =>
{
// TranslatePropertyBindings is turned on if diagnostics are enabled so that
// property binding issues can be reported, even if the property bindings are
// not actually wanted by the client.
// Generate property bindings only if the diagnostics object was requested.
// This is because the binding information is output in the diagnostics object
// so there's no point translating bindings if the diagnostics object
// isn't available.
var makeColorsBindable = diagnostics != null && options.HasFlag(LottieVisualOptions.BindableColors);
translationResult = LottieToWinCompTranslator.TryTranslateLottieComposition(
lottieComposition: lottieComposition,
configuration: new TranslatorConfiguration
{
TranslatePropertyBindings = options.HasFlag(LottieVisualOptions.IncludeDiagnostics),
TranslatePropertyBindings = makeColorsBindable,
GenerateColorBindings = makeColorsBindable,
TargetUapVersion = GetCurrentUapVersion(),
});
@ -141,6 +141,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie
{
diagnostics.TranslationIssues = ToIssues(translationResult.TranslationIssues);
diagnostics.TranslationTime = timeMeasurer.GetElapsedAndRestart();
// If there were any property bindings, save them in the Diagnostics object.
if (translationResult.SourceMetadata.TryGetValue(s_propertyBindingNamesKey, out var propertyBindingNames))
{
diagnostics.ThemePropertyBindings = (IReadOnlyList<PropertyBinding>)propertyBindingNames;
}
}
// Optimize the resulting translation. This will usually significantly reduce the size of

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

@ -4,7 +4,9 @@
using System;
using System.Collections.Generic;
using Microsoft.Toolkit.Uwp.UI.Lottie.CompMetadata;
using Microsoft.Toolkit.Uwp.UI.Lottie.LottieData;
using Windows.UI.Composition;
namespace Microsoft.Toolkit.Uwp.UI.Lottie
{
@ -47,9 +49,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie
/// </summary>
public LottieVisualOptions Options { get; internal set; }
public KeyValuePair<string, double>[] Markers { get; internal set; } = Array.Empty<KeyValuePair<string, double>>();
// Holds the parsed LottieComposition. Only used if one of the codegen or XML options was selected.
// Holds the parsed LottieComposition.
internal LottieComposition LottieComposition { get; set; }
// Holds the translated Visual. Only used if one of the codegen or XML options was selected.
@ -59,6 +59,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie
// XML options was selected.
internal uint RequiredUapVersion { get; set; }
// CompostionPropertySet that holds the theming properties.
internal CompositionPropertySet ThemingPropertySet { get; set; }
// Describes the property bindings in the ThemingPropertySet.
internal IReadOnlyList<PropertyBinding> ThemePropertyBindings { get; set; }
internal LottieVisualDiagnostics Clone() =>
new LottieVisualDiagnostics
{
@ -67,14 +73,16 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie
JsonParsingIssues = JsonParsingIssues,
LottieComposition = LottieComposition,
LottieValidationIssues = LottieValidationIssues,
Markers = Markers,
Options = Options,
ParseTime = ParseTime,
ReadTime = ReadTime,
RequiredUapVersion = RequiredUapVersion,
RootVisual = RootVisual,
ThemePropertyBindings = ThemePropertyBindings,
ThemingPropertySet = ThemingPropertySet,
TranslationIssues = TranslationIssues,
TranslationTime = TranslationTime,
ValidationTime = ValidationTime,
TranslationIssues = TranslationIssues,
};
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
}

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

@ -29,9 +29,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie
/// </summary>
IncludeDiagnostics = 2,
/// <summary>
/// Bind each distinct color of the Lottie to a property set value so that
/// the colors can be dynamically updated.
/// </summary>
BindableColors = 4,
/// <summary>
/// Enables all options.
/// </summary>
All = IncludeDiagnostics | Optimize,
All = BindableColors | IncludeDiagnostics | Optimize,
}
}

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

@ -316,7 +316,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
{
// A color binding string was found. Bind the color to a property with the
// name described by the binding string.
return TranslateBoundSolidColor(context, opacity, bindingName, DefaultValueOf(color));
return TranslateBoundSolidColor(context, opacity, bindingName, displayName: bindingName, DefaultValueOf(color));
}
if (context.Translation.ColorPalette != null && !color.IsAnimated)
@ -325,13 +325,20 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
// the name of the color in the palette.
var paletteColor = color.InitialValue;
var paletteColorAsWinUIColor = ConvertTo.Color(paletteColor);
if (!context.Translation.ColorPalette.TryGetValue(paletteColor, out bindingName))
{
bindingName = $"Color_{ConvertTo.Color(paletteColor).HexWithoutAlpha}";
bindingName = $"Color_{paletteColorAsWinUIColor.HexWithoutAlpha}";
context.Translation.ColorPalette.Add(paletteColor, bindingName);
}
return TranslateBoundSolidColor(context, opacity, bindingName, paletteColor);
return TranslateBoundSolidColor(
context,
opacity,
bindingName,
displayName: $"#{paletteColorAsWinUIColor.R:X2}{paletteColorAsWinUIColor.G:X2}{paletteColorAsWinUIColor.B:X2}",
paletteColor);
}
// Do not generate a binding for this color.
@ -343,10 +350,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
LayerContext context,
CompositeOpacity opacity,
string bindingName,
string displayName,
Color defaultColor)
{
// Ensure there is a property added to the theme property set.
ThemePropertyBindings.EnsureColorThemePropertyExists(context, bindingName, defaultColor);
ThemePropertyBindings.EnsureColorThemePropertyExists(context, bindingName, displayName, defaultColor);
var result = context.ObjectFactory.CreateColorBrush();

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

@ -5,7 +5,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData.MetaData;
using Microsoft.Toolkit.Uwp.UI.Lottie.CompMetadata;
namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
{
@ -14,22 +14,20 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
// Identifies the bound property names in TranslationResult.SourceMetadata.
static readonly Guid s_propertyBindingNamesKey = new Guid("A115C46A-254C-43E6-A3C7-9DE516C3C3C8");
readonly List<(string bindingName, PropertySetValueType actualType, PropertySetValueType exposedType, object initialValue)> _names =
new List<(string bindingName, PropertySetValueType actualType, PropertySetValueType exposedType, object initialValue)>();
readonly List<PropertyBinding> _propertyBindings = new List<PropertyBinding>();
// Adds the current list of property bindings to the source metadata dictionary.
internal void AddToSourceMetadata(Dictionary<Guid, object> sourceMetadata)
{
if (_names.Count > 0)
if (_propertyBindings.Count > 0)
{
// Add the binding descriptions, ordered by binding name.
sourceMetadata.Add(s_propertyBindingNamesKey, _names.OrderBy(n => n.Item1).ToArray());
sourceMetadata.Add(s_propertyBindingNamesKey, _propertyBindings.OrderBy(n => n.BindingName).ToArray());
}
}
// Adds a property binding to the list of property bindings.
internal void AddPropertyBinding(string bindingName, PropertySetValueType actualType, PropertySetValueType exposedType, object defaultValue)
=> _names.Add((bindingName, actualType, exposedType, defaultValue));
internal void AddPropertyBinding(PropertyBinding propertyBinding) => _propertyBindings.Add(propertyBinding);
// Parses the given binding string and returns the binding name for the given property, or
// null if not found. Returns the first matching binding name (there could be more than

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

@ -45,7 +45,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
/// <summary>
/// Ensures there is a property in the theme property set with the given name and default value.
/// </summary>
public static void EnsureColorThemePropertyExists(LayerContext context, string bindingName, Color defaultValue)
public static void EnsureColorThemePropertyExists(LayerContext context, string bindingName, string displayName, Color defaultValue)
{
var defaultValueAsWinUIColor = ConvertTo.Color(defaultValue);
var defaultValueAsVector4 = ConvertTo.Vector4(defaultValueAsWinUIColor);
@ -57,11 +57,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
case CompositionGetValueStatus.NotFound:
// The property hasn't been added yet. Add it.
themePropertySet.InsertVector4(bindingName, ConvertTo.Vector4(defaultValueAsWinUIColor));
context.Translation.PropertyBindings.AddPropertyBinding(
bindingName,
actualType: PropertySetValueType.Vector4,
exposedType: PropertySetValueType.Color,
defaultValue: defaultValueAsWinUIColor);
context.Translation.PropertyBindings.AddPropertyBinding(new CompMetadata.PropertyBinding
{
BindingName = bindingName,
DisplayName = displayName,
ActualType = PropertySetValueType.Vector4,
ExposedType = PropertySetValueType.Color,
DefaultValue = defaultValueAsWinUIColor,
});
break;
case CompositionGetValueStatus.Succeeded:
@ -84,7 +87,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
/// <summary>
/// Ensures there is a property in the theme property set with the given name and default value.
/// </summary>
static void EnsureScalarThemePropertyExists(TranslationContext context, string bindingName, double defaultValue)
static void EnsureScalarThemePropertyExists(TranslationContext context, string bindingName, string displayName, double defaultValue)
{
var defaultValueAsFloat = ConvertTo.Float(defaultValue);
var themePropertySet = GetThemePropertySet(context);
@ -95,11 +98,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
case CompositionGetValueStatus.NotFound:
// The property hasn't been added yet. Add it.
themePropertySet.InsertScalar(bindingName, defaultValueAsFloat);
context.PropertyBindings.AddPropertyBinding(
bindingName,
actualType: PropertySetValueType.Scalar,
exposedType: PropertySetValueType.Scalar,
defaultValue: ConvertTo.Float(defaultValue));
context.PropertyBindings.AddPropertyBinding(new CompMetadata.PropertyBinding
{
BindingName = bindingName,
DisplayName = displayName,
ActualType = PropertySetValueType.Scalar,
ExposedType = PropertySetValueType.Scalar,
DefaultValue = ConvertTo.Float(defaultValue),
});
break;
case CompositionGetValueStatus.Succeeded:
@ -134,7 +140,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
else
{
// Ensure there is a property in the theme property set for this binding name.
EnsureScalarThemePropertyExists(context, bindingName, defaultValue);
EnsureScalarThemePropertyExists(context, bindingName, displayName: bindingName, defaultValue);
// Create an expression that binds property to the theme property set.
var anim = context.ObjectFactory.CreateExpressionAnimation(ExpressionFactory.ThemedScalar(bindingName));

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

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Microsoft.Toolkit.Uwp.UI.Lottie.CompMetadata;
using Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData;
using Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData.MetaData;
using Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData.Mgcg;
@ -164,19 +165,19 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
{
if (Info.GenerateDependencyObject)
{
builder.WriteLine($"public {ExposedType(prop)} {prop.Name}");
builder.WriteLine($"public {ExposedType(prop)} {prop.BindingName}");
builder.OpenScope();
builder.WriteLine($"get => ({ExposedType(prop)})GetValue({prop.Name}Property);");
builder.WriteLine($"set => SetValue({prop.Name}Property, value);");
builder.WriteLine($"get => ({ExposedType(prop)})GetValue({prop.BindingName}Property);");
builder.WriteLine($"set => SetValue({prop.BindingName}Property, value);");
}
else
{
builder.WriteLine($"public {ExposedType(prop)} {prop.Name}");
builder.WriteLine($"public {ExposedType(prop)} {prop.BindingName}");
builder.OpenScope();
builder.WriteLine($"get => _theme{prop.Name};");
builder.WriteLine($"get => _theme{prop.BindingName};");
builder.WriteLine("set");
builder.OpenScope();
builder.WriteLine($"_theme{prop.Name} = value;");
builder.WriteLine($"_theme{prop.BindingName} = value;");
builder.WriteLine($"if ({Info.ThemePropertiesFieldName} != null)");
builder.OpenScope();
WriteThemePropertyInitialization(builder, Info.ThemePropertiesFieldName, prop);
@ -196,12 +197,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
{
foreach (var prop in Info.SourceMetadata.PropertyBindings)
{
builder.WriteComment($"Dependency property for {prop.Name}.");
builder.WriteLine($"public static readonly DependencyProperty {prop.Name}Property =");
builder.WriteComment($"Dependency property for {prop.BindingName}.");
builder.WriteLine($"public static readonly DependencyProperty {prop.BindingName}Property =");
builder.Indent();
builder.WriteLine($"DependencyProperty.Register({String(prop.Name)}, typeof({ExposedType(prop)}), typeof({Info.ClassName}),");
builder.WriteLine($"DependencyProperty.Register({String(prop.BindingName)}, typeof({ExposedType(prop)}), typeof({Info.ClassName}),");
builder.Indent();
builder.WriteLine($"new PropertyMetadata({GetDefaultPropertyBindingValue(prop)}, On{prop.Name}Changed));");
builder.WriteLine($"new PropertyMetadata({GetDefaultPropertyBindingValue(prop)}, On{prop.BindingName}Changed));");
builder.UnIndent();
builder.UnIndent();
builder.WriteLine();
@ -217,7 +218,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
{
foreach (var prop in Info.SourceMetadata.PropertyBindings)
{
builder.WriteLine($"static void On{prop.Name}Changed(DependencyObject d, DependencyPropertyChangedEventArgs args)");
builder.WriteLine($"static void On{prop.BindingName}Changed(DependencyObject d, DependencyPropertyChangedEventArgs args)");
builder.OpenScope();
WriteThemePropertyInitialization(builder, $"(({Info.ClassName})d)._themeProperties?", prop, $"({ExposedType(prop)})args.NewValue");
builder.CloseScope();
@ -319,7 +320,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
{
var defaultValue = GetDefaultPropertyBindingValue(prop);
WriteInitializedField(builder, ExposedType(prop), $"_theme{prop.Name}", _s.VariableInitialization($"c_theme{prop.Name}"));
WriteInitializedField(builder, ExposedType(prop), $"_theme{prop.BindingName}", _s.VariableInitialization($"c_theme{prop.BindingName}"));
}
}
@ -373,7 +374,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
// Initialize the values in the property set.
foreach (var prop in Info.SourceMetadata.PropertyBindings)
{
WriteThemePropertyInitialization(builder, Info.ThemePropertiesFieldName, prop, prop.Name);
WriteThemePropertyInitialization(builder, Info.ThemePropertiesFieldName, prop, prop.BindingName);
}
builder.CloseScope();

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

@ -68,8 +68,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
{
if (SourceInfo.GenerateDependencyObject)
{
builder.Private.WriteLine($"static {WinUINamespace}::Xaml::DependencyProperty^ _{prop.Name}Property;");
builder.Private.WriteLine($"static void On{prop.Name}Changed({WinUINamespace}::Xaml::DependencyObject^ d, {WinUINamespace}::Xaml::DependencyPropertyChangedEventArgs^ e);");
builder.Private.WriteLine($"static {WinUINamespace}::Xaml::DependencyProperty^ _{prop.BindingName}Property;");
builder.Private.WriteLine($"static void On{prop.BindingName}Changed({WinUINamespace}::Xaml::DependencyObject^ d, {WinUINamespace}::Xaml::DependencyPropertyChangedEventArgs^ e);");
}
else
{
@ -85,7 +85,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
_ => throw new InvalidOperationException(),
};
WriteInitializedField(builder.Private, exposedTypeName, $"_theme{prop.Name}", S.VariableInitialization(initialValue));
WriteInitializedField(builder.Private, exposedTypeName, $"_theme{prop.BindingName}", S.VariableInitialization(initialValue));
}
}
@ -96,8 +96,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
// Write properties declarations for each themed property.
foreach (var prop in SourceInfo.SourceMetadata.PropertyBindings)
{
builder.Internal.WriteLine($"{QualifiedTypeName(prop.ExposedType)} {prop.Name}();");
builder.Internal.WriteLine($"void {prop.Name}({QualifiedTypeName(prop.ExposedType)} value);");
builder.Internal.WriteLine($"{QualifiedTypeName(prop.ExposedType)} {prop.BindingName}();");
builder.Internal.WriteLine($"void {prop.BindingName}({QualifiedTypeName(prop.ExposedType)} value);");
}
builder.Internal.WriteLine();
@ -147,17 +147,17 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
foreach (var prop in propertyBindings)
{
// Write the getter. This just reads the values out of the backing field.
builder.WriteLine($"{TypeName(prop.ExposedType)} {sourceClassQualifier}{prop.Name}()");
builder.WriteLine($"{TypeName(prop.ExposedType)} {sourceClassQualifier}{prop.BindingName}()");
builder.OpenScope();
builder.WriteLine($"return _theme{prop.Name};");
builder.WriteLine($"return _theme{prop.BindingName};");
builder.CloseScope();
builder.WriteLine();
// Write the setter. This saves to the backing field, and updates the theme property
// set if one has been created.
builder.WriteLine($"void {sourceClassQualifier}{prop.Name}({TypeName(prop.ExposedType)} value)");
builder.WriteLine($"void {sourceClassQualifier}{prop.BindingName}({TypeName(prop.ExposedType)} value)");
builder.OpenScope();
builder.WriteLine($"_theme{prop.Name} = value;");
builder.WriteLine($"_theme{prop.BindingName} = value;");
builder.WriteLine("if (_themeProperties != nullptr)");
builder.OpenScope();
WriteThemePropertyInitialization(builder, "_themeProperties", prop);

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

@ -77,19 +77,19 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
if (SourceInfo.GenerateDependencyObject)
{
builder.Private.WriteLine($"static {WinUINamespace}::Xaml::DependencyProperty^ _{S.CamelCase(prop.Name)}Property;");
builder.Private.WriteLine($"static void On{prop.Name}Changed({WinUINamespace}::Xaml::DependencyObject^ d, {WinUINamespace}::Xaml::DependencyPropertyChangedEventArgs^ e);");
builder.Internal.WriteLine($"static {WinUINamespace}::Xaml::DependencyProperty^ {prop.Name}Property();");
builder.Private.WriteLine($"static {WinUINamespace}::Xaml::DependencyProperty^ _{S.CamelCase(prop.BindingName)}Property;");
builder.Private.WriteLine($"static void On{prop.BindingName}Changed({WinUINamespace}::Xaml::DependencyObject^ d, {WinUINamespace}::Xaml::DependencyPropertyChangedEventArgs^ e);");
builder.Internal.WriteLine($"static {WinUINamespace}::Xaml::DependencyProperty^ {prop.BindingName}Property();");
builder.Internal.WriteLine();
}
else
{
var exposedTypeName = QualifiedTypeName(prop.ExposedType);
WriteInitializedField(builder.Private, exposedTypeName, $"_theme{prop.Name}", S.VariableInitialization($"c_theme{prop.Name}"));
WriteInitializedField(builder.Private, exposedTypeName, $"_theme{prop.BindingName}", S.VariableInitialization($"c_theme{prop.BindingName}"));
}
builder.Internal.WriteLine($"property {QualifiedTypeName(prop.ExposedType)} {prop.Name}");
builder.Internal.WriteLine($"property {QualifiedTypeName(prop.ExposedType)} {prop.BindingName}");
builder.Internal.OpenScope();
builder.Internal.WriteLine($"{QualifiedTypeName(prop.ExposedType)} get();");
builder.Internal.WriteLine($"void set ({QualifiedTypeName(prop.ExposedType)} value);");
@ -134,7 +134,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
// Initialize the values in the property set.
foreach (var prop in propertyBindings)
{
WriteThemePropertyInitialization(builder, SourceInfo.ThemePropertiesFieldName, prop, prop.Name);
WriteThemePropertyInitialization(builder, SourceInfo.ThemePropertiesFieldName, prop, prop.BindingName);
}
builder.CloseScope();
@ -162,14 +162,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
if (SourceInfo.GenerateDependencyObject)
{
// Write the dependency property accessor.
builder.WriteLine($"DependencyProperty^ {sourceClassQualifier}{prop.Name}Property()");
builder.WriteLine($"DependencyProperty^ {sourceClassQualifier}{prop.BindingName}Property()");
builder.OpenScope();
builder.WriteLine($"return _{S.CamelCase(prop.Name)}Property;");
builder.WriteLine($"return _{S.CamelCase(prop.BindingName)}Property;");
builder.CloseScope();
builder.WriteLine();
// Write the dependency property change handler.
builder.WriteLine($"void {sourceClassQualifier}On{prop.Name}Changed(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)");
builder.WriteLine($"void {sourceClassQualifier}On{prop.BindingName}Changed(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)");
builder.OpenScope();
builder.WriteLine($"auto self = ({sourceClassQualifier}^)d;");
builder.WriteLine();
@ -181,49 +181,49 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
builder.WriteLine();
// Write the dependency property initializer.
builder.WriteLine($"DependencyProperty^ {sourceClassQualifier}_{S.CamelCase(prop.Name)}Property =");
builder.WriteLine($"DependencyProperty^ {sourceClassQualifier}_{S.CamelCase(prop.BindingName)}Property =");
builder.Indent();
builder.WriteLine($"DependencyProperty::Register(");
builder.Indent();
builder.WriteLine($"{S.String(prop.Name)},");
builder.WriteLine($"{S.String(prop.BindingName)},");
builder.WriteLine($"{TypeName(prop.ExposedType)}::typeid,");
builder.WriteLine($"{sourceClassQualifier}typeid,");
builder.WriteLine($"ref new PropertyMetadata(c_theme{prop.Name},");
builder.WriteLine($"ref new PropertyChangedCallback(&{sourceClassQualifier}On{prop.Name}Changed)));");
builder.WriteLine($"ref new PropertyMetadata(c_theme{prop.BindingName},");
builder.WriteLine($"ref new PropertyChangedCallback(&{sourceClassQualifier}On{prop.BindingName}Changed)));");
builder.UnIndent();
builder.UnIndent();
builder.WriteLine();
}
// Write the getter.
builder.WriteLine($"{TypeName(prop.ExposedType)} {sourceClassQualifier}{prop.Name}::get()");
builder.WriteLine($"{TypeName(prop.ExposedType)} {sourceClassQualifier}{prop.BindingName}::get()");
builder.OpenScope();
if (SourceInfo.GenerateDependencyObject)
{
// Get the value from the dependency property.
builder.WriteLine($"return ({TypeName(prop.ExposedType)})GetValue(_{S.CamelCase(prop.Name)}Property);");
builder.WriteLine($"return ({TypeName(prop.ExposedType)})GetValue(_{S.CamelCase(prop.BindingName)}Property);");
}
else
{
// Get the value from the backing field.
builder.WriteLine($"return _theme{prop.Name};");
builder.WriteLine($"return _theme{prop.BindingName};");
}
builder.CloseScope();
builder.WriteLine();
// Write the setter.
builder.WriteLine($"void {sourceClassQualifier}{prop.Name}::set({TypeName(prop.ExposedType)} value)");
builder.WriteLine($"void {sourceClassQualifier}{prop.BindingName}::set({TypeName(prop.ExposedType)} value)");
builder.OpenScope();
if (SourceInfo.GenerateDependencyObject)
{
builder.WriteLine($"SetValue(_{S.CamelCase(prop.Name)}Property, value);");
builder.WriteLine($"SetValue(_{S.CamelCase(prop.BindingName)}Property, value);");
}
else
{
// This saves to the backing field, and updates the theme property
// set if one has been created.
builder.WriteLine($"_theme{prop.Name} = value;");
builder.WriteLine($"_theme{prop.BindingName} = value;");
builder.WriteLine("if (_themeProperties != nullptr)");
builder.OpenScope();
WriteThemePropertyInitialization(builder, "_themeProperties", prop);

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

@ -8,6 +8,7 @@ using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Text;
using Microsoft.Toolkit.Uwp.UI.Lottie.CompMetadata;
using Microsoft.Toolkit.Uwp.UI.Lottie.GenericData;
using Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Tables;
using Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools;
@ -334,7 +335,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
builder,
propertySetVariableName,
prop,
$"_theme{prop.Name}");
$"_theme{prop.BindingName}");
/// <summary>
/// Writes code that initializes a theme property value in the theme property set.
@ -346,7 +347,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
string themePropertyAccessor)
{
var propertyValueAccessor = GetThemePropertyAccessor(themePropertyAccessor, prop);
builder.WriteLine($"{propertySetVariableName}{Deref}Insert{PropertySetValueTypeName(prop.ActualType)}({String(prop.Name)}, {propertyValueAccessor});");
builder.WriteLine($"{propertySetVariableName}{Deref}Insert{PropertySetValueTypeName(prop.ActualType)}({String(prop.BindingName)}, {propertyValueAccessor});");
}
/// <summary>
@ -404,10 +405,10 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
switch (themeProperty.ExposedType)
{
case PropertySetValueType.Color:
yield return new NamedConstant($"c_theme{themeProperty.Name}", $"Theme property: {themeProperty.Name}.", ConstantType.Color, (Wui.Color)themeProperty.DefaultValue);
yield return new NamedConstant($"c_theme{themeProperty.BindingName}", $"Theme property: {themeProperty.BindingName}.", ConstantType.Color, (Wui.Color)themeProperty.DefaultValue);
break;
case PropertySetValueType.Scalar:
yield return new NamedConstant($"c_theme{themeProperty.Name}", $"Theme property: {themeProperty.Name}.", ConstantType.Float, (float)themeProperty.DefaultValue);
yield return new NamedConstant($"c_theme{themeProperty.BindingName}", $"Theme property: {themeProperty.BindingName}.", ConstantType.Float, (float)themeProperty.DefaultValue);
break;
case PropertySetValueType.Vector2:
case PropertySetValueType.Vector3:

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

@ -1,31 +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 Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData.MetaData;
namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
{
sealed class PropertyBinding
{
internal PropertyBinding(
string bindingName,
PropertySetValueType actualType,
PropertySetValueType exposedType,
object defaultValue)
{
Name = bindingName;
ActualType = actualType;
ExposedType = exposedType;
DefaultValue = defaultValue;
}
internal string Name { get; }
internal PropertySetValueType ActualType { get; }
internal PropertySetValueType ExposedType { get; }
internal object DefaultValue { get; }
}
}

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

@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Toolkit.Uwp.UI.Lottie.CompMetadata;
using Microsoft.Toolkit.Uwp.UI.Lottie.LottieMetadata;
using Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData.MetaData;
@ -42,10 +43,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
{
if (_sourceMetadata.TryGetValue(s_propertyBindingNamesKey, out var propertyBindingNames))
{
var list = (IReadOnlyList<(string bindingName, PropertySetValueType actualType, PropertySetValueType exposedType, object initialValue)>)propertyBindingNames;
_propertyBindings = list.Select(item => new PropertyBinding(item.bindingName, item.actualType, item.exposedType, item.initialValue))
.OrderBy(pb => pb.Name)
.ToArray();
var list = (IReadOnlyList<PropertyBinding>)propertyBindingNames;
_propertyBindings = list.OrderBy(pb => pb.BindingName).ToArray();
}
else
{

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

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Microsoft.Toolkit.Uwp.UI.Lottie.CompMetadata;
namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Tables
{
@ -22,6 +23,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Tables
Row.HeaderTop,
new Row.ColumnData(
ColumnData.Create("Theme property"),
ColumnData.Create("Accessor"),
ColumnData.Create("Type"),
ColumnData.Create("Default value")
),
@ -31,7 +33,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Tables
var records =
(from property in themeProperty
select new Row.ColumnData(
ColumnData.Create(property.Name, TextAlignment.Left, 1),
ColumnData.Create(property.DisplayName, TextAlignment.Left, 1),
ColumnData.Create(property.BindingName, TextAlignment.Left, 1),
ColumnData.Create(property.ExposedType.ToString()),
ColumnData.Create(GetDefaultValueString(property))
)).ToArray();

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

@ -26,7 +26,6 @@
<Compile Include="$(MSBuildThisFileDirectory)CodeGen\MatrixDecomposer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CodeGen\NamedConstant.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CodeGen\NodeNamer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CodeGen\PropertyBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CodeGen\SourceMetadata.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CodeGen\Stringifier.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CodeGen\Tables\ColumnData.cs" />