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 all 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
7 changes: 6 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,9 @@
_SHCONTF
SIGDN
SHGDNF
SIATTRIBFLAGS
SIATTRIBFLAGS
IInitializeWithFile
IInitializeWithItem
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"
IsEnabled="False" IsTabStop="True" GotFocus="UserControl_GotFocus"
ActualThemeChanged="{x:Bind UpdatePreviewerTheme}">

</UserControl>
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// 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 CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.Shell;
using Windows.Win32.UI.WindowsAndMessaging;

namespace Peek.FilePreviewer.Controls
{
[INotifyPropertyChanged]
public unsafe sealed partial class ShellPreviewHandlerControl : UserControl
{
// Mica fallback colors
private static readonly COLORREF LightThemeBgColor = new(0x00f3f3f3);
private static readonly COLORREF DarkThemeBgColor = new(0x00202020);

private static readonly HBRUSH LightThemeBgBrush = PInvoke.CreateSolidBrush(LightThemeBgColor);
private static readonly HBRUSH DarkThemeBgBrush = PInvoke.CreateSolidBrush(DarkThemeBgColor);

[ObservableProperty]
private IPreviewHandler? source;

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

public event EventHandler? HandlerLoaded;

public event EventHandler? HandlerError;

public static readonly DependencyProperty HandlerVisibilityProperty = DependencyProperty.Register(
nameof(HandlerVisibility),
typeof(Visibility),
typeof(ShellPreviewHandlerControl),
new PropertyMetadata(Visibility.Collapsed, new PropertyChangedCallback((d, e) => ((ShellPreviewHandlerControl)d).OnHandlerVisibilityChanged())));

// Must have its own visibility property so resize events can still fire
public Visibility HandlerVisibility
{
get { return (Visibility)GetValue(HandlerVisibilityProperty); }
set { SetValue(HandlerVisibilityProperty, value); }
}

public ShellPreviewHandlerControl()
{
InitializeComponent();

containerWndProc = ContainerWndProc;
}

partial void OnSourceChanged(IPreviewHandler? value)
{
if (Source != null)
{
UpdatePreviewerTheme();

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

HandlerLoaded?.Invoke(this, EventArgs.Empty);
}
catch
{
HandlerError?.Invoke(this, EventArgs.Empty);
}
}
}

private void OnHandlerVisibilityChanged()
{
if (HandlerVisibility == Visibility.Visible)
{
PInvoke.ShowWindow(containerHwnd, SHOW_WINDOW_CMD.SW_SHOW);
IsEnabled = true;

// Clears the background from the last previewer
// The brush can only be drawn here because flashes will occur during resize
PInvoke.SetClassLongPtr(containerHwnd, GET_CLASS_LONG_INDEX.GCLP_HBRBACKGROUND, containerBgBrush);
PInvoke.UpdateWindow(containerHwnd);
PInvoke.SetClassLongPtr(containerHwnd, GET_CLASS_LONG_INDEX.GCLP_HBRBACKGROUND, IntPtr.Zero);
PInvoke.InvalidateRect(containerHwnd, (RECT*)null, true);
}
else
{
PInvoke.ShowWindow(containerHwnd, SHOW_WINDOW_CMD.SW_HIDE);
IsEnabled = false;
}
}

private void UpdatePreviewerTheme()
{
COLORREF bgColor, fgColor;
switch (ActualTheme)
{
case ElementTheme.Light:
bgColor = LightThemeBgColor;
fgColor = new COLORREF(0x00000000); // Black

containerBgBrush = LightThemeBgBrush;
break;

case ElementTheme.Dark:
default:
bgColor = DarkThemeBgColor;
fgColor = new COLORREF(0x00FFFFFF); // White

containerBgBrush = DarkThemeBgBrush;
break;
}

if (Source is IPreviewHandlerVisuals visuals)
{
visuals.SetBackgroundColor(bgColor);
visuals.SetTextColor(fgColor);

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

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
{
}
}
}
}
9 changes: 8 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,13 @@
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"
Source="{x:Bind ShellPreviewHandlerPreviewer.Preview, Mode=OneWay}"
HandlerVisibility="{x:Bind IsPreviewVisible(ShellPreviewHandlerPreviewer, Previewer.State), Mode=OneWay}"
HandlerLoaded="ShellPreviewHandlerPreview_HandlerLoaded"
HandlerError="ShellPreviewHandlerPreview_HandlerError" />

<Image
x:Name="ImagePreview"
MaxWidth="{x:Bind ImagePreviewer.MaxImageSize.Width, Mode=OneWay}"
Expand Down Expand Up @@ -59,7 +66,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
22 changes: 22 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 Expand Up @@ -268,6 +274,22 @@ private void PreviewBrowser_NavigationCompleted(WebView2 sender, CoreWebView2Nav
}
}

private void ShellPreviewHandlerPreview_HandlerLoaded(object sender, EventArgs e)
{
if (ShellPreviewHandlerPreviewer != null)
{
ShellPreviewHandlerPreviewer.State = PreviewState.Loaded;
}
}

private void ShellPreviewHandlerPreview_HandlerError(object sender, EventArgs e)
{
if (ShellPreviewHandlerPreviewer != null)
{
ShellPreviewHandlerPreviewer.State = PreviewState.Error;
}
}

private async void KeyboardAccelerator_CtrlC_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
{
if (Previewer != null)
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
}
14 changes: 14 additions & 0 deletions src/modules/peek/Peek.FilePreviewer/NativeMethods.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
IClassFactory
CoGetClassObject
CreateSolidBrush
CreateWindowEx
DefWindowProc
GetDpiForWindow
InvalidateRect
RegisterClass
SetClassLongPtr
SetLayeredWindowAttributes
SetWindowPos
ShowWindow
UpdateWindow
SHCreateItemFromParsingName
Loading