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 4 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% %*
Binary file added components/OpacityMaskView/samples/Assets/Owl.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@niels9001 / @Arlodotexe just need latest icon here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michael-hawker updated.

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\Owl.jpg" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\Owl.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]
37 changes: 37 additions & 0 deletions components/OpacityMaskView/samples/OpacityMaskViewSample.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!-- 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>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Offset="0" Color="White" />
<GradientStop Offset="1" Color="Transparent" />
</LinearGradientBrush>
</controls:OpacityMaskView.OpacityMask>
<Button Content="Hello Windows Community Toolkit" />
</controls:OpacityMaskView>
<controls:OpacityMaskView>
<controls:OpacityMaskView.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Offset="0" Color="White" />
<GradientStop Offset="0.6" Color="Transparent" />
</LinearGradientBrush>
</controls:OpacityMaskView.OpacityMask>
<Border Width="96"
Height="96"
BorderBrush="Red"
BorderThickness="1"
CornerRadius="12">
<Image Source="ms-appx:///Assets/Owl.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>
107 changes: 107 additions & 0 deletions components/OpacityMaskView/src/OpacityMaskView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// 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>
/// TODO
/// </summary>
[TemplatePart(Name = RootGridTemplateName, Type = typeof(Grid))]
[TemplatePart(Name = MaskRectangleTemplateName, Type = typeof(Rectangle))]
[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(Brush), typeof(OpacityMaskView), new PropertyMetadata(null, OnOpacityMaskChanged));

private const string ContentPresenterTemplateName = "PART_ContentPresenter";
private const string MaskRectangleTemplateName = "PART_MaskRectangle";
private const string RootGridTemplateName = "PART_RootGrid";

private readonly Compositor _compositor = Window.Current.Compositor;
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 an opacity mask, as a <see cref="Brush"/> implementation that is applied to any alpha-channel masking for the rendered content of the content.
/// </summary>
public Brush? OpacityMask
{
get => (Brush?)GetValue(OpacityMaskProperty);
set => SetValue(OpacityMaskProperty, value);
}

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

Grid rootGrid = (Grid)GetTemplateChild(RootGridTemplateName);
ContentPresenter contentPresenter = (ContentPresenter)GetTemplateChild(ContentPresenterTemplateName);
Rectangle maskRectangle = (Rectangle)GetTemplateChild(MaskRectangleTemplateName);

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

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to apply any of this like we do for the AttachedCardShadow/AttachedShadow or UIElementExtensions.VisualFactory components in the Toolkit, so it's just an extension that can be applied to any element directly vs. a ContentControl?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michael-hawker I don't think we can do this. Sergio created one like UIElementExtensions.OpacityMask in store, but the position of the redirectVisual is not aligned with the source control at some times. When this happens, we use a Border to wrap the content to fix. I think the one in the toolkit should be more generic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds similar to what we have to do for the AttachedDropShadow at times. I think having specific requirements can be fine, it's just extensions are a lot easier to add into a design than having to add another layer of depth to the Visual Tree to all cases and setup the layout. It's probably a case of a templated control vs. a raw-element with the CardShadow too I'd imagine. What are your thoughts @Sergio0694?

}

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;
}

Brush? opacityMask = (Brush?)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}">
<Rectangle x:Name="PART_MaskRectangle"
Fill="{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>
Loading