Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Experiment] OpacityMaskView #491

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c18f789
init commit
h82258652 Aug 22, 2023
22fb628
commit
h82258652 Aug 22, 2023
3bcc44f
update
h82258652 Aug 22, 2023
2041235
apply xaml styler
h82258652 Aug 22, 2023
b267e56
rename asset file
h82258652 Aug 24, 2023
ad14d18
Merge branch 'main' into opacitymaskview-experiment
h82258652 Aug 31, 2023
54e0799
Fix CI
h82258652 Sep 1, 2023
dc3427a
Merge branch 'opacitymaskview-experiment' of https://github.com/h8225…
h82258652 Sep 1, 2023
ce46c6f
fix get compositor on windows app sdk
h82258652 Sep 16, 2023
dafd7fb
Merge branch 'main' into opacitymaskview-experiment
h82258652 Nov 30, 2023
4d8f969
Merge branch 'main' into opacitymaskview-experiment
h82258652 Dec 25, 2023
5adf13a
Merge branch 'main' into opacitymaskview-experiment
h82258652 Jan 25, 2024
1decd44
Merge branch 'main' into opacitymaskview-experiment
h82258652 Apr 3, 2024
73bbb47
Merge branch 'main' into opacitymaskview-experiment
h82258652 Apr 11, 2024
9b2268e
Merge branch 'main' into opacitymaskview-experiment
Arlodotexe May 27, 2024
9ec8111
Merge branch 'main' into opacitymaskview-experiment
h82258652 Jul 12, 2024
aa4539f
update icon
h82258652 Jul 12, 2024
f022067
Merge branch 'main' into opacitymaskview-experiment
h82258652 Jul 24, 2024
fd37449
Merge branch 'main' into opacitymaskview-experiment
h82258652 Aug 1, 2024
8e0da1e
Merge branch 'main' into opacitymaskview-experiment
h82258652 Aug 7, 2024
38db553
Merge branch 'main' into opacitymaskview-experiment
h82258652 Aug 22, 2024
c2d6602
Merge branch 'main' into opacitymaskview-experiment
h82258652 Aug 26, 2024
0440794
update to the latest store version
h82258652 Sep 4, 2024
a23366c
Merge branch 'main' into opacitymaskview-experiment
h82258652 Sep 14, 2024
1965f53
Merge branch 'main' into opacitymaskview-experiment
h82258652 Sep 23, 2024
2fb74f9
Merge branch 'main' into opacitymaskview-experiment
h82258652 Oct 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions components/OpacityMaskView/OpenSolution.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@ECHO OFF

powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %*
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions components/OpacityMaskView/samples/Dependencies.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!--
WinUI 2 under UWP uses TargetFramework uap10.0.*
WinUI 3 under WinAppSdk uses TargetFramework net6.0-windows10.*
However, under Uno-powered platforms, both WinUI 2 and 3 can share the same TargetFramework.

MSBuild doesn't play nicely with this out of the box, so we've made it easy for you.

For .NET Standard packages, you can use the Nuget Package Manager in Visual Studio.
For UWP / WinAppSDK / Uno packages, place the package references here.
-->
<Project>
<!-- WinUI 2 / UWP -->
<ItemGroup Condition="'$(IsUwp)' == 'true'">
<!-- <PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls.Primitives" Version="7.1.2"/> -->
</ItemGroup>

<!-- WinUI 2 / Uno -->
<ItemGroup Condition="'$(IsUno)' == 'true' AND '$(WinUIMajorVersion)' == '2'">
<!-- <PackageReference Include="Uno.Microsoft.Toolkit.Uwp.UI.Controls.Primitives" Version="7.1.11"/> -->
</ItemGroup>

<!-- WinUI 3 / WinAppSdk -->
<ItemGroup Condition="'$(IsWinAppSdk)' == 'true'">
<!-- <PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Primitives" Version="7.1.2"/> -->
</ItemGroup>

<!-- WinUI 3 / Uno -->
<ItemGroup Condition="'$(IsUno)' == 'true' AND '$(WinUIMajorVersion)' == '3'">
<!-- <PackageReference Include="Uno.CommunityToolkit.WinUI.UI.Controls.Primitives" Version="7.1.100-dev.15.g12261e2626"/> -->
</ItemGroup>
</Project>
9 changes: 9 additions & 0 deletions components/OpacityMaskView/samples/MultiTarget.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project>
<PropertyGroup>
<!--
MultiTarget is a custom property that indicates which target a project is designed to be built for / run on.
Used to create project references, generate solution files, enable/disable TargetFrameworks, and build nuget packages.
-->
<MultiTarget>uwp;wasdk;</MultiTarget>
</PropertyGroup>
</Project>
16 changes: 16 additions & 0 deletions components/OpacityMaskView/samples/OpacityMaskView.Samples.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="MSBuild.Sdk.Extras/3.0.23">
<PropertyGroup>
<ToolkitComponentName>OpacityMaskView</ToolkitComponentName>
</PropertyGroup>

<!-- Sets this up as a toolkit component's sample project -->
<Import Project="$(ToolingDirectory)\ToolkitComponent.SampleProject.props" />
<ItemGroup>
<None Remove="Assets\Owl3.jpg" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\Owl3.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
18 changes: 18 additions & 0 deletions components/OpacityMaskView/samples/OpacityMaskView.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: OpacityMaskView
author: h82258652
description: A control that applies an opacity mask to its content.
keywords: OpacityMaskView, Control, Layout
dev_langs:
- csharp
category: Controls
subcategory: Layout
discussion-id: 490
issue-id: 0
icon: assets/icon.png
---
# OpacityMaskView

The `OpacityMaskView` control applies an opacity mask to its content. The mask is defined by the `OpacityMask` property, which can contain a `Brush` object. The `OpacityMask` property is typically set to a `LinearGradientBrush` or `ImageBrush` object.

> [!Sample OpacityMaskViewSample]
45 changes: 45 additions & 0 deletions components/OpacityMaskView/samples/OpacityMaskViewSample.xaml
Original file line number Diff line number Diff line change
@@ -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. -->
<Page x:Class="OpacityMaskViewExperiment.Samples.OpacityMaskViewSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:OpacityMaskViewExperiment.Samples"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<StackPanel Spacing="12">
<controls:OpacityMaskView>
<controls:OpacityMaskView.OpacityMask>
<Rectangle>
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Offset="0" Color="White" />
<GradientStop Offset="1" Color="Transparent" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</controls:OpacityMaskView.OpacityMask>
<Button Content="Hello Windows Community Toolkit" />
</controls:OpacityMaskView>
<controls:OpacityMaskView>
<controls:OpacityMaskView.OpacityMask>
<Rectangle>
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Offset="0" Color="White" />
<GradientStop Offset="0.6" Color="Transparent" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</controls:OpacityMaskView.OpacityMask>
<Border Width="96"
Height="96"
BorderBrush="Red"
BorderThickness="1"
CornerRadius="12">
<Image Source="ms-appx:///Assets/Owl3.jpg" />
</Border>
</controls:OpacityMaskView>
</StackPanel>
</Page>
19 changes: 19 additions & 0 deletions components/OpacityMaskView/samples/OpacityMaskViewSample.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// 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 CommunityToolkit.WinUI.Controls;

namespace OpacityMaskViewExperiment.Samples;

/// <summary>
/// An example sample page of a custom control inheriting from Panel.
/// </summary>
[ToolkitSample(id: nameof(OpacityMaskViewSample), "OpacityMaskView sample", description: $"A sample for showing how to create and use a {nameof(OpacityMaskView)} control.")]
public sealed partial class OpacityMaskViewSample : Page
{
public OpacityMaskViewSample()
{
this.InitializeComponent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="MSBuild.Sdk.Extras/3.0.23">
<PropertyGroup>
<ToolkitComponentName>OpacityMaskView</ToolkitComponentName>
<Description>This package contains OpacityMaskView.</Description>

<!-- Rns suffix is required for namespaces shared across projects. See https://github.com/CommunityToolkit/Labs-Windows/issues/152 -->
<RootNamespace>CommunityToolkit.WinUI.Controls.OpacityMaskViewRns</RootNamespace>
</PropertyGroup>

<!-- Sets this up as a toolkit component's source project -->
<Import Project="$(ToolingDirectory)\ToolkitComponent.SourceProject.props" />
</Project>
31 changes: 31 additions & 0 deletions components/OpacityMaskView/src/Dependencies.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!--
WinUI 2 under UWP uses TargetFramework uap10.0.*
WinUI 3 under WinAppSdk uses TargetFramework net6.0-windows10.*
However, under Uno-powered platforms, both WinUI 2 and 3 can share the same TargetFramework.

MSBuild doesn't play nicely with this out of the box, so we've made it easy for you.

For .NET Standard packages, you can use the Nuget Package Manager in Visual Studio.
For UWP / WinAppSDK / Uno packages, place the package references here.
-->
<Project>
<!-- WinUI 2 / UWP -->
<ItemGroup Condition="'$(IsUwp)' == 'true'">
<!-- <PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls.Primitives" Version="7.1.2"/> -->
</ItemGroup>

<!-- WinUI 2 / Uno -->
<ItemGroup Condition="'$(IsUno)' == 'true' AND '$(WinUIMajorVersion)' == '2'">
<!-- <PackageReference Include="Uno.Microsoft.Toolkit.Uwp.UI.Controls.Primitives" Version="7.1.11"/> -->
</ItemGroup>

<!-- WinUI 3 / WinAppSdk -->
<ItemGroup Condition="'$(IsWinAppSdk)' == 'true'">
<!-- <PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Primitives" Version="7.1.2"/> -->
</ItemGroup>

<!-- WinUI 3 / Uno -->
<ItemGroup Condition="'$(IsUno)' == 'true' AND '$(WinUIMajorVersion)' == '3'">
<!-- <PackageReference Include="Uno.CommunityToolkit.WinUI.UI.Controls.Primitives" Version="7.1.100-dev.15.g12261e2626"/> -->
</ItemGroup>
</Project>
9 changes: 9 additions & 0 deletions components/OpacityMaskView/src/MultiTarget.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project>
<PropertyGroup>
<!--
MultiTarget is a custom property that indicates which target a project is designed to be built for / run on.
Used to create project references, generate solution files, enable/disable TargetFrameworks, and build nuget packages.
-->
<MultiTarget>uwp;wasdk;</MultiTarget>
</PropertyGroup>
</Project>
111 changes: 111 additions & 0 deletions components/OpacityMaskView/src/OpacityMaskView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Numerics;
#if WINDOWS_WINAPPSDK
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml.Hosting;
using Microsoft.UI.Xaml.Shapes;
#else
using Windows.UI.Composition;
using Windows.UI.Xaml.Hosting;
using Windows.UI.Xaml.Shapes;
#endif

namespace CommunityToolkit.WinUI.Controls;

/// <summary>
/// A control that applies an opacity mask to its content.
/// </summary>
[TemplatePart(Name = RootGridTemplateName, Type = typeof(Grid))]
[TemplatePart(Name = MaskContainerTemplateName, Type = typeof(Border))]
[TemplatePart(Name = ContentPresenterTemplateName, Type = typeof(ContentPresenter))]
public partial class OpacityMaskView : ContentControl
{
/// <summary>
/// Identifies the <see cref="OpacityMask"/> property.
/// </summary>
public static readonly DependencyProperty OpacityMaskProperty =
DependencyProperty.Register(nameof(OpacityMask), typeof(UIElement), typeof(OpacityMaskView), new PropertyMetadata(null, OnOpacityMaskChanged));

private const string ContentPresenterTemplateName = "PART_ContentPresenter";
private const string MaskContainerTemplateName = "PART_MaskContainer";
private const string RootGridTemplateName = "PART_RootGrid";

#if WINDOWS_WINAPPSDK
private readonly Compositor _compositor = CompositionTarget.GetCompositorForCurrentThread();
#else
private readonly Compositor _compositor = Window.Current.Compositor;
#endif
private CompositionBrush? _mask;
private CompositionMaskBrush? _maskBrush;

/// <summary>
/// Creates a new instance of the <see cref="OpacityMaskView"/> class.
/// </summary>
public OpacityMaskView()
{
DefaultStyleKey = typeof(OpacityMaskView);
}

/// <summary>
/// Gets or sets a <see cref="UIElement"/> as the opacity mask that is applied to alpha-channel masking for the rendered content of the content.
/// </summary>
public UIElement? OpacityMask
{
get => (UIElement?)GetValue(OpacityMaskProperty);
set => SetValue(OpacityMaskProperty, value);
}

/// <inheritdoc />
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();

Grid rootGrid = (Grid)GetTemplateChild(RootGridTemplateName);
ContentPresenter contentPresenter = (ContentPresenter)GetTemplateChild(ContentPresenterTemplateName);
Border maskContainer = (Border)GetTemplateChild(MaskContainerTemplateName);

_maskBrush = _compositor.CreateMaskBrush();
_maskBrush.Source = GetVisualBrush(contentPresenter);
_mask = GetVisualBrush(maskContainer);
_maskBrush.Mask = OpacityMask is null ? null : _mask;

SpriteVisual redirectVisual = _compositor.CreateSpriteVisual();
redirectVisual.RelativeSizeAdjustment = Vector2.One;
redirectVisual.Brush = _maskBrush;
ElementCompositionPreview.SetElementChildVisual(rootGrid, redirectVisual);
}

private static CompositionBrush GetVisualBrush(UIElement element)
{
Visual visual = ElementCompositionPreview.GetElementVisual(element);

Compositor compositor = visual.Compositor;

CompositionVisualSurface visualSurface = compositor.CreateVisualSurface();
visualSurface.SourceVisual = visual;
ExpressionAnimation sourceSizeAnimation = compositor.CreateExpressionAnimation($"{nameof(visual)}.Size");
sourceSizeAnimation.SetReferenceParameter(nameof(visual), visual);
visualSurface.StartAnimation(nameof(visualSurface.SourceSize), sourceSizeAnimation);

CompositionSurfaceBrush brush = compositor.CreateSurfaceBrush(visualSurface);

visual.Opacity = 0;

return brush;
}

private static void OnOpacityMaskChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
OpacityMaskView self = (OpacityMaskView)d;
if (self._maskBrush is not { } maskBrush)
{
return;
}

UIElement? opacityMask = (UIElement?)e.NewValue;
maskBrush.Mask = opacityMask is null ? null : self._mask;
}
}
32 changes: 32 additions & 0 deletions components/OpacityMaskView/src/OpacityMaskView.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!-- 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. -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CommunityToolkit.WinUI.Controls">

<Style BasedOn="{StaticResource DefaultOpacityMaskViewStyle}"
TargetType="local:OpacityMaskView" />

<Style x:Key="DefaultOpacityMaskViewStyle"
TargetType="local:OpacityMaskView">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:OpacityMaskView">
<Grid x:Name="PART_RootGrid"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<Border x:Name="PART_MaskContainer"
Child="{TemplateBinding OpacityMask}"
IsHitTestVisible="False" />
<ContentPresenter x:Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

</ResourceDictionary>
9 changes: 9 additions & 0 deletions components/OpacityMaskView/src/Themes/Generic.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls">

<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.OpacityMaskView/OpacityMaskView.xaml" />
</ResourceDictionary.MergedDictionaries>

</ResourceDictionary>
11 changes: 11 additions & 0 deletions components/OpacityMaskView/tests/OpacityMaskView.Tests.projitems
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>04511143-C2A3-4893-810D-3C1EA2877430</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>OpacityMaskViewExperiment.Tests</Import_RootNamespace>
</PropertyGroup>
</Project>
13 changes: 13 additions & 0 deletions components/OpacityMaskView/tests/OpacityMaskView.Tests.shproj
Original file line number Diff line number Diff line change
@@ -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>04511143-C2A3-4893-810D-3C1EA2877430</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="OpacityMaskView.Tests.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>