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

[Peek] Add support for preview handlers #28690

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
5 changes: 5 additions & 0 deletions .github/actions/spell-check/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1430,6 +1430,7 @@ ppsi
ppsid
ppsrm
ppsrree
ppstm
ppsz
pptal
ppv
Expand Down Expand Up @@ -1472,6 +1473,8 @@ psfi
Psr
psrm
psrree
pstatstg
pstm
pstr
pstream
pstrm
Expand Down Expand Up @@ -1826,6 +1829,7 @@ STDMETHODCALLTYPE
STDMETHODIMP
stefan
Stereolithography
STGC
STGM
STGMEDIUM
sticpl
Expand Down Expand Up @@ -2191,6 +2195,7 @@ wnd
WNDCLASS
WNDCLASSEX
WNDCLASSEXW
WNDCLASSW
WNDPROC
wordpad
workaround
Expand Down
6 changes: 5 additions & 1 deletion src/modules/peek/Peek.Common/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@
_SHCONTF
SIGDN
SHGDNF
SIATTRIBFLAGS
SIATTRIBFLAGS
IInitializeWithFile
IInitializeWithStream
IPreviewHandler
IPreviewHandlerVisuals
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!-- Copyright (c) Microsoft Corporation. All rights reserved. -->
<!-- Licensed under the MIT License. See LICENSE in the project root for license information. -->

<UserControl
x:Class="Peek.FilePreviewer.Controls.ShellPreviewHandlerControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Peek.FilePreviewer.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Loaded="UserControl_Loaded"
EffectiveViewportChanged="UserControl_EffectiveViewportChanged"
IsTabStop="True" GotFocus="UserControl_GotFocus"
ActualThemeChanged="{x:Bind UpdatePreviewerTheme}">

</UserControl>
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Runtime.CompilerServices;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Peek.FilePreviewer.Previewers;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.Shell;
using Windows.Win32.UI.WindowsAndMessaging;

namespace Peek.FilePreviewer.Controls
{
public unsafe sealed partial class ShellPreviewHandlerControl : UserControl
{
// Making this into a DependencyProperty causes a InvalidCastException
private IPreviewHandler? _source;

private HWND containerHwnd;
private WNDPROC containerWndProc;
private RECT controlRect;

public static readonly DependencyProperty LoadingStateProperty = DependencyProperty.Register(
nameof(LoadingState),
typeof(PreviewState),
typeof(ShellPreviewHandlerControl),
new PropertyMetadata(PreviewState.Uninitialized));

public PreviewState? LoadingState
{
get { return (PreviewState)GetValue(LoadingStateProperty); }
set { SetValue(LoadingStateProperty, value); }
}

public IPreviewHandler? Source
{
get => _source;
set
{
_source = value;
SourcePropertyChanged();
}
}

public ShellPreviewHandlerControl()
{
InitializeComponent();

containerWndProc = ContainerWndProc;
}

private void SourcePropertyChanged()
{
if (Source != null)
{
UpdatePreviewerTheme();

try
{
// Attach the preview handler to the container window
Source.SetWindow(containerHwnd, (RECT*)Unsafe.AsPointer(ref controlRect));
Source.DoPreview();

PInvoke.ShowWindow(containerHwnd, SHOW_WINDOW_CMD.SW_SHOW);
}
catch
{
}
}
else
{
PInvoke.ShowWindow(containerHwnd, SHOW_WINDOW_CMD.SW_HIDE);
}
}

private void UpdatePreviewerTheme()
{
if (Source is IPreviewHandlerVisuals visuals)
{
try
{
switch (ActualTheme)
{
case ElementTheme.Light:
visuals.SetBackgroundColor(new COLORREF(0x00f3f3f3));
dillydylann marked this conversation as resolved.
Show resolved Hide resolved
visuals.SetTextColor(new COLORREF(0x00000000));
break;

case ElementTheme.Dark:
visuals.SetBackgroundColor(new COLORREF(0x00202020));
visuals.SetTextColor(new COLORREF(0x00FFFFFF));
break;
}

// Changing the previewer colors might not always redraw itself
PInvoke.InvalidateRect(containerHwnd, (RECT*)null, true);
}
catch
{
}
}
}

private LRESULT ContainerWndProc(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam)
{
// Here for future use :)
return PInvoke.DefWindowProc(hWnd, msg, wParam, lParam);
}

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
fixed (char* pContainerClassName = "PeekShellPreviewHandlerContainer")
dillydylann marked this conversation as resolved.
Show resolved Hide resolved
{
PInvoke.RegisterClass(new WNDCLASSW()
{
lpfnWndProc = containerWndProc,
lpszClassName = pContainerClassName,
});

// Create the container window to host the preview handler
containerHwnd = PInvoke.CreateWindowEx(
WINDOW_EX_STYLE.WS_EX_LAYERED,
pContainerClassName,
null,
WINDOW_STYLE.WS_CHILD,
0, // X
0, // Y
0, // Width
0, // Height
(HWND)Win32Interop.GetWindowFromWindowId(XamlRoot.ContentIslandEnvironment.AppWindowId), // Peek UI window
HMENU.Null,
HINSTANCE.Null);

// Allows the preview handlers to display properly
PInvoke.SetLayeredWindowAttributes(containerHwnd, default, byte.MaxValue, LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_ALPHA);
}
}

private void UserControl_EffectiveViewportChanged(FrameworkElement sender, EffectiveViewportChangedEventArgs args)
{
var dpi = (float)PInvoke.GetDpiForWindow(containerHwnd) / 96;

// Resize the container window
PInvoke.SetWindowPos(
containerHwnd,
(HWND)0, // HWND_TOP
(int)(Math.Abs(args.EffectiveViewport.X) * dpi),
(int)(Math.Abs(args.EffectiveViewport.Y) * dpi),
(int)(ActualWidth * dpi),
(int)(ActualHeight * dpi),
SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE);

// Resize the preview handler window
controlRect.right = (int)(ActualWidth * dpi);
controlRect.bottom = (int)(ActualHeight * dpi);
try
{
Source?.SetRect((RECT*)Unsafe.AsPointer(ref controlRect));
}
catch
{
}

// Resizing the previewer might not always redraw itself
PInvoke.InvalidateRect(containerHwnd, (RECT*)null, false);
}

private void UserControl_GotFocus(object sender, RoutedEventArgs e)
{
try
{
Source?.SetFocus();
}
catch
{
}
}
}
}
7 changes: 6 additions & 1 deletion src/modules/peek/Peek.FilePreviewer/FilePreview.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
VerticalAlignment="Center"
IsActive="{x:Bind MatchPreviewState(Previewer.State, previewers:PreviewState.Loading), Mode=OneWay}" />

<controls:ShellPreviewHandlerControl
dillydylann marked this conversation as resolved.
Show resolved Hide resolved
x:Name="ShellPreviewHandlerPreview"
LoadingState="{x:Bind ShellPreviewHandlerPreviewer.State, Mode=OneWay}"
Source="{x:Bind ShellPreviewHandlerPreviewer.Preview, Mode=OneWay}" />

<Image
x:Name="ImagePreview"
MaxWidth="{x:Bind ImagePreviewer.MaxImageSize.Width, Mode=OneWay}"
Expand Down Expand Up @@ -59,7 +64,7 @@
DirectoryCount="{x:Bind ArchivePreviewer.DirectoryCountText, Mode=OneWay}"
Size="{x:Bind ArchivePreviewer.SizeText, Mode=OneWay}"
Visibility="{x:Bind IsPreviewVisible(ArchivePreviewer, Previewer.State), Mode=OneWay}" />

<controls:UnsupportedFilePreview
x:Name="UnsupportedFilePreview"
LoadingState="{x:Bind UnsupportedFilePreviewer.State, Mode=OneWay}"
Expand Down
6 changes: 6 additions & 0 deletions src/modules/peek/Peek.FilePreviewer/FilePreview.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public sealed partial class FilePreview : UserControl, IDisposable
[NotifyPropertyChangedFor(nameof(VideoPreviewer))]
[NotifyPropertyChangedFor(nameof(BrowserPreviewer))]
[NotifyPropertyChangedFor(nameof(ArchivePreviewer))]
[NotifyPropertyChangedFor(nameof(ShellPreviewHandlerPreviewer))]
[NotifyPropertyChangedFor(nameof(UnsupportedFilePreviewer))]

private IPreviewer? previewer;
Expand Down Expand Up @@ -96,6 +97,8 @@ private async void Previewer_PropertyChanged(object? sender, System.ComponentMod

public IArchivePreviewer? ArchivePreviewer => Previewer as IArchivePreviewer;

public IShellPreviewHandlerPreviewer? ShellPreviewHandlerPreviewer => Previewer as IShellPreviewHandlerPreviewer;

public IUnsupportedFilePreviewer? UnsupportedFilePreviewer => Previewer as IUnsupportedFilePreviewer;

public IFileSystemItem Item
Expand Down Expand Up @@ -220,6 +223,9 @@ partial void OnPreviewerChanging(IPreviewer? value)
ArchivePreview.Source = null;
BrowserPreview.Source = null;

ShellPreviewHandlerPreviewer?.Clear();
ShellPreviewHandlerPreview.Source = null;

if (Previewer != null)
{
Previewer.PropertyChanged -= Previewer_PropertyChanged;
Expand Down
4 changes: 4 additions & 0 deletions src/modules/peek/Peek.FilePreviewer/NativeMethods.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"public": false
}
8 changes: 8 additions & 0 deletions src/modules/peek/Peek.FilePreviewer/NativeMethods.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CreateWindowEx
DefWindowProc
GetDpiForWindow
InvalidateRect
RegisterClass
SetLayeredWindowAttributes
SetWindowPos
ShowWindow
12 changes: 12 additions & 0 deletions src/modules/peek/Peek.FilePreviewer/Peek.FilePreviewer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PRIResource Include="..\Peek.UI\Strings\en-US\Resources.resw" Link="Strings\en-US\Resources.resw">
Expand All @@ -17,6 +18,7 @@
<ItemGroup>
<None Remove="Controls\ArchiveControl.xaml" />
<None Remove="Controls\BrowserControl.xaml" />
<None Remove="Controls\ShellPreviewHandlerControl.xaml" />
<None Remove="Controls\UnsupportedFilePreview\FailedFallbackPreviewControl.xaml" />
<None Remove="Controls\UnsupportedFilePreview\InformationalPreviewControl.xaml" />
<None Remove="FilePreview.xaml" />
Expand All @@ -29,6 +31,10 @@
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="SharpCompress" />
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="Microsoft.Windows.CsWin32">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
Expand All @@ -38,6 +44,12 @@
<ProjectReference Include="..\Peek.Common\Peek.Common.csproj" />
</ItemGroup>

<ItemGroup>
<Page Update="Controls\ShellPreviewHandlerControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>

<ItemGroup>
<Page Update="Controls\ArchiveControl.xaml">
<Generator>MSBuild:Compile</Generator>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Windows.Win32.UI.Shell;

namespace Peek.FilePreviewer.Previewers
{
public interface IShellPreviewHandlerPreviewer : IPreviewer
{
public IPreviewHandler? Preview { get; }

public void Clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ public IPreviewer Create(IFileSystemItem file)
{
return new ArchivePreviewer(file);
}
else if (ShellPreviewHandlerPreviewer.IsFileTypeSupported(file.Extension))
{
return new ShellPreviewHandlerPreviewer(file);
}

// Other previewer types check their supported file types here
return CreateDefaultPreviewer(file);
Expand Down
Loading